欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

玩转 SpringBoot 2 之整合 JWT 下篇

程序员文章站 2023-08-23 10:19:27
前言 在 "《玩转 SpringBoot 2 之整合 JWT 上篇》" 中介绍了关于 JWT 相关概念和JWT 基本使用的操作方式。本文为 SpringBoot 整合 JWT 的下篇,通过解决 App 用户登录 Session 问题的实战操作,带你更深入理解 JWT。通过本文你还可以了解到如下内容: ......

前言

《玩转 springboot 2 之整合 jwt 上篇》 中介绍了关于 jwt 相关概念和jwt 基本使用的操作方式。本文为 springboot 整合 jwt 的下篇,通过解决 app 用户登录 session 问题的实战操作,带你更深入理解 jwt。通过本文你还可以了解到如下内容:

  1. springboot 使用拦截器的实际应用
  2. springboot 统一异常处理
  3. springboot 快速搭建 restful api

关于生成jwt 操作请参考 《玩转 springboot 2 之整合 jwt 上篇》

实战操作

登录操作

登录操作流程图:

玩转 SpringBoot 2 之整合 JWT 下篇

登录操作流程介绍:

  1. app 根据用户名和密码访问登录接口。
  2. 如果用户名和密码错误则提示 app 用户密码输入错误。
  3. 如果用户名和密码正确则获取用户信息(表示登录成功)并根据用户信息生成 token 并将其存入servletcontext 中。
  4. 将生成的 token 返回给 app。

登录操作具体代码:

@restcontroller
public class logincontroller {
    logger log = loggerfactory.getlogger(logincontroller.class);
    @autowired
    private jwtservice jwtservice;
    
    @requestmapping("/login")
    public returnmessage<object> login(string loginname,string password,httpservletrequest request) {
        if(valid(loginname,password)) {
            returnmessageutil.error(codeenum.loginnameandpwderror);
        }
        
        map<string,string> userinfo = createuserinfomap(loginname,password);
        string token = jwtservice.createtoken(userinfo, 1);
        
        servletcontext context = request.getservletcontext();
        context.setattribute(token, token);
        log.info("token:"+token);
        return returnmessageutil.sucess(token);
    }
} 

    private map<string,string> createuserinfomap(string loginname, string password) {
        map<string,string> userinfo = new hashmap<string,string>();
        userinfo.put("loginname", loginname);
        userinfo.put("password", password);
        return userinfo;
    }

    private boolean valid(string loginname, string password) {
        if(objects.equal("ljk", loginname) && objects.equal("123456", password) ) {
            return true;
        }
        return false;
    }

拦截操作

拦截操作流程图:

玩转 SpringBoot 2 之整合 JWT 下篇

拦截操作流程介绍:

  1. 服务器获取 (app访问具体的api 时携带的 token)token,如果 token 为空则提示 app token不能为空。
  2. 如果 token 不为空则从 servletcontext 中获取 token,如果不存在则提示 app 该token为非法 token !

  3. 如果 token 不为空并且 servletcontext 中存在该token,需要判断 token 是否过期。如果未过期则放开拦截。

  4. 如果token 已经过期则提示 app token已经过期,需要重新登录。

拦截操作具体代码:

public class logininterceptor implements handlerinterceptor{
    
    logger log = loggerfactory.getlogger(logininterceptor.class);
    
    private jwtservice jwtservice;
    
    public logininterceptor(jwtservice jwtservice) {
        this.jwtservice = jwtservice;
    }
    
    public boolean prehandle(httpservletrequest request, httpservletresponse response, object o) throws exception {
        
        log.info("token checkout processing");
        string token = request.getparameter("token");
        
        if (stringutils.isempty(token)) {
            throw new jkexception(codeenum.tokenisempty);
        }
        
        string tokeninservletcontext = (string)request.getservletcontext().getattribute(token);
        if(stringutils.isempty(tokeninservletcontext)) {
            throw new jkexception(codeenum.illegaltoken);
        }
        
        try {
             jwtservice.verifytoken(token);
        } catch (algorithmmismatchexception  e) {
            log.error("token checkout processing algorithmmismatchexception 异常!"+e.getlocalizedmessage());
            throw new jkexception(codeenum.illegaltoken);
        }catch (tokenexpiredexception  e) {
            log.info("token已经过期");
            throw new jkexception(codeenum.expiretoken);
        }catch (signatureverificationexception  e) {
            log.error("token checkout processing signatureverificationexception 异常!"+e.getlocalizedmessage());
            throw new jkexception(codeenum.illegaltoken);
         }catch (exception e) {
            log.error("token checkout processing 未知异常!"+e.getlocalizedmessage());
            throw e;
        }
        
        return true;
    }
}

退出操作

退出操作流程介绍:

访问退出接口并传递登录生成的 token,然后将 servletcontext中的 token 删除。

退出操作具体代码:

    @getmapping("/logout")
    public returnmessage<?> logout(string token,string issuer,httpservletrequest request) {
        servletcontext context = request.getservletcontext();
        context.removeattribute(token);
        return returnmessageutil.sucess();
    }

公共代码

indexcontroller app 访问测试api,具体代码如下:

@restcontroller
public class indexcontroller {
    
    @getmapping("index")
    public returnmessage index() {
        return returnmessageutil.sucess();
    }
}

统一异常次处理的 handle

@restcontrolleradvice
public class exceptionhandle {
    private final static logger logger = loggerfactory.getlogger(exceptionhandle.class);
    @exceptionhandler(value = exception.class)
    //@responsebody
    public returnmessage<object> handle(httpservletresponse response, exception exception) {
         response.setcharacterencoding("utf-8");
        if(exception instanceof jkexception) {
            jkexception sbexception = (jkexception)exception;
            return returnmessageutil.error(sbexception.getcode(), sbexception.getmessage());
        }else {
            logger.error("系统异常 {}",exception);
            return returnmessageutil.error(-1, "未知异常"+exception.getmessage());
        }
    }
}

jwtservice 工具类 代码可以在我的github上进行查看,具体地址请查看下面代码示例章节。

测试

这里使用postman 进行测试,当然你也可以选用你顺手的工具进行测试哈!

访问 http://localhost:8080/sbe/login?loginname=ljk&password=123456 进行登录获取token,如下图所示date字段的值就是登录成功后生成的 token。

玩转 SpringBoot 2 之整合 JWT 下篇

访问 http://localhost:8080/sbe/index?token=具体token值,如下图所示访问成功!
玩转 SpringBoot 2 之整合 JWT 下篇

如果不携带 token 会提示token不能为空,如下图所示:

玩转 SpringBoot 2 之整合 JWT 下篇

如果输入不存在的 token 则提示 非法token!,如下图所示:

玩转 SpringBoot 2 之整合 JWT 下篇

http://localhost:8080/sbe/logout?token=具体token值 进行退出,如下图所示:

玩转 SpringBoot 2 之整合 JWT 下篇

退出后再次使用已经退出的token 访问,会提示非法token 如下图所示:

玩转 SpringBoot 2 之整合 JWT 下篇

小结

登录操作通过 jwt 生成token 返回给app,拦截操作(也可以理解成校验操作)通过拦截器(handlerinterceptor)来进行实现。最后退出操作是通过将token 保存servletcontent 中,退出其实就是将 token 从 servletcontent 中删除。

本文主旨是通过简单实现,带你了解 app 认证过程处理方式,对于拦截部分你也可以通过 filter 或 aop 来进行实现。token 存储也可以考虑使用redis来实现,还有一个问题就是:jwt 续期问题本文并没有实现(jwt 过期时间延期问题)。这个部分就当成一个作业,欢迎大家在评论区说说你的解决方案?

代码示例

本文并没有对 jwtservice 工具类、统一异常处理、拦截器使用搭建进行详细介绍,如果你想直接查看本文全部源码,请在我的github 仓库 springbootexamples 中的 spring-boot-2.x-jwt 模块进行查看。

github:

同时你也可以通过查看我关于拦截器、统一异常处理、搭建 restful api 详细教程总结自己完成相关的实现: