玩转 SpringBoot 2 之整合 JWT 下篇
前言
在《玩转 springboot 2 之整合 jwt 上篇》 中介绍了关于 jwt 相关概念和jwt 基本使用的操作方式。本文为 springboot 整合 jwt 的下篇,通过解决 app 用户登录 session 问题的实战操作,带你更深入理解 jwt。通过本文你还可以了解到如下内容:
- springboot 使用拦截器的实际应用
- springboot 统一异常处理
- springboot 快速搭建 restful api
关于生成jwt 操作请参考 《玩转 springboot 2 之整合 jwt 上篇》
实战操作
登录操作
登录操作流程图:
登录操作流程介绍:
- app 根据用户名和密码访问登录接口。
- 如果用户名和密码错误则提示 app 用户密码输入错误。
- 如果用户名和密码正确则获取用户信息(表示登录成功)并根据用户信息生成 token 并将其存入servletcontext 中。
- 将生成的 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; }
拦截操作
拦截操作流程图:
拦截操作流程介绍:
- 服务器获取 (app访问具体的api 时携带的 token)token,如果 token 为空则提示 app token不能为空。
如果 token 不为空则从 servletcontext 中获取 token,如果不存在则提示 app 该token为非法 token !
如果 token 不为空并且 servletcontext 中存在该token,需要判断 token 是否过期。如果未过期则放开拦截。
如果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。
访问 http://localhost:8080/sbe/index?token=具体token值,如下图所示访问成功!
如果不携带 token 会提示token不能为空,如下图所示:
如果输入不存在的 token 则提示 非法token!,如下图所示:
http://localhost:8080/sbe/logout?token=具体token值 进行退出,如下图所示:
退出后再次使用已经退出的token 访问,会提示非法token 如下图所示:
小结
登录操作通过 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 详细教程总结自己完成相关的实现:
下一篇: 小程序加盟有什么优势?前景如何?
推荐阅读
-
SpringBoot 2.x 开发案例之 Shiro 整合 Redis
-
玩转 SpringBoot 2 之整合 JWT 上篇
-
玩转 SpringBoot 2 之整合 JWT 下篇
-
玩转 SpringBoot 2 快速整合拦截器
-
玩转SpringBoot 2 之项目启动篇
-
SpringBoot2.x系列教程59--SpringBoot整合消息队列之JMS简介
-
SpringBoot2.x系列教程63--SpringBoot整合消息队列之RabbitMQ详解
-
SpringBoot2.x系列教程55--NoSQL之SpringBoot整合ElasticSearch方式二
-
SpringBoot2.x系列教程50--NoSQL之SpringBoot整合Redis
-
SpringBoot2.x系列教程54--NoSQL之SpringBoot整合ElasticSearch方式一