![f852f104611311384897f5e657da0425.png](https://img-blog.csdnimg.cn/img_convert/f852f104611311384897f5e657da0425.png)
在前面的文章中,我们已经使用 token 实现前后端分离的系统登录及访问鉴权。
第二十四章:整合SpringSecurity之最简登录及方法鉴权
第二十五章:整合SpringSecurity之使用数据库实现登录鉴权
第二十六章:整合SpringSecurity之JSON格式前后端交互
第二十七章:整合SpringSecurity之前后端分离使用Token实现登录鉴权
登录成功后,服务端会生成一个 token 并存储起来,这样客户端携 token 再次访问时,服务端就可以根据 token 获取当前用户的登录状态及用户信息。在之前的示例中,我们将 token 存储在数据库中,客户端每次访问都需要从数据库中读取 token 相关联的用户信息。数据库通常是系统的性能瓶颈,每次请求都需要先访问数据库,对数据库来说压力山大。
当然,我们使用 Redis 之类的高性能中间件来存储 token,这样相对来说,压力来会减少,但是压力依然在。那么有没有一种技术,在服务端获取到 token 之后,可以直接根据 token 解析出用户信息,这样就可以避免从存储中间件获取信息给系统造成的压力。
JWT(Java Web Token)就是这样的一种技术。
相关知识
什么是 JWT
请参考阮一峰博客 http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html,里面有比较详尽的介绍。
目标
整合 SpringSecurity 实现使用 JWT 进行登录及访问鉴权。
准备工作
创建用户表 user、角色表 role、用户角色关系表 user_role,因为 JWT 本身就是用户信息,所以不用再另行存储,可以直接解析
![d3c72ef3d15e7ad62b4faba41138fe02.png](https://img-blog.csdnimg.cn/img_convert/d3c72ef3d15e7ad62b4faba41138fe02.png)
操作步骤
添加依赖
引入 Spring Boot Starter 父工程
![5a5b5fcd4a8717571b808453a85946f4.png](https://img-blog.csdnimg.cn/img_convert/5a5b5fcd4a8717571b808453a85946f4.png)
添加 springSecurity 及 mybatisPlus 的依赖,添加后的整体依赖如下
![47aee52f8963c8161e86ef2e905fd002.png](https://img-blog.csdnimg.cn/img_convert/47aee52f8963c8161e86ef2e905fd002.png)
配置
配置一下数据源
![9bfc61bca449fd507c601fff89e388af.png](https://img-blog.csdnimg.cn/img_convert/9bfc61bca449fd507c601fff89e388af.png)
编码
实体类
角色实体类 Role,实现权限接口 GrantedAuthority
![44f68f7d36c39cdb0ccc624d801b7f62.png](https://img-blog.csdnimg.cn/img_convert/44f68f7d36c39cdb0ccc624d801b7f62.png)
用户实体类 user,实现权限接口 UserDetails,主要方法是 getAuthorities,用于获取用户的角色列表
![1505a0f12ef0878b04af89483e6d27b4.png](https://img-blog.csdnimg.cn/img_convert/1505a0f12ef0878b04af89483e6d27b4.png)
用户角色关系实体
![109776746aaed92022086577f715fda0.png](https://img-blog.csdnimg.cn/img_convert/109776746aaed92022086577f715fda0.png)
Repository 层
分别为三个实体类添加 Mapper
![884e0354de93808bed117260d69b8671.png](https://img-blog.csdnimg.cn/img_convert/884e0354de93808bed117260d69b8671.png)
实现 UserDetailsService 接口
UserDetailsService 是 SpringSecurity 提供的登陆时用于根据用户名获取用户信息的接口
![94fb38b541e5fdf1f8d176446c484925.png](https://img-blog.csdnimg.cn/img_convert/94fb38b541e5fdf1f8d176446c484925.png)
自定义登录参数格式
![7656751ce6452d8a3f4d76040f6f05f0.png](https://img-blog.csdnimg.cn/img_convert/7656751ce6452d8a3f4d76040f6f05f0.png)
自定义登录过滤器
继承 SpringSecurity 提供的 AbstractAuthenticationProcessingFilter 类,实现 attemptAuthentication 方法,用于登录校验。
本例中,模拟前端使用 json 格式传递参数,所以通过 objectMapper.readValue 的方式从流中获取入参,之后借用了用户名密码登录的校验,
如果鉴权成功,使用 JWT 工具类生成 token 并将 token 返回给前端。
![c2c83c0c01f1fe6ec614d2d1903d603e.png](https://img-blog.csdnimg.cn/img_convert/c2c83c0c01f1fe6ec614d2d1903d603e.png)
自定义登陆成功后处理
实现 SpringSecurity 提供的 AuthenticationSuccessHandler 接口,使用 JSON 格式返回
![87cfab35d62ad23edc20ba30ad708302.png](https://img-blog.csdnimg.cn/img_convert/87cfab35d62ad23edc20ba30ad708302.png)
自定义登陆失败后处理
实现 SpringSecurity 提供的 AuthenticationFailureHandler 接口,使用 JSON 格式返回
![5e843d24494a80bf78b5f91665e6127c.png](https://img-blog.csdnimg.cn/img_convert/5e843d24494a80bf78b5f91665e6127c.png)
自定义权限校验失败后处理
登陆成功之后,访问接口之前 SpringSecurity 会进行鉴权,如果没有访问权限,需要对返回进行处理。实现 SpringSecurity 提供的 AccessDeniedHandler 接口,使用 JSON 格式返回
![91a16054ca0ebe087a6dbd7ce708b1a2.png](https://img-blog.csdnimg.cn/img_convert/91a16054ca0ebe087a6dbd7ce708b1a2.png)
自定义未登录后处理
实现 SpringSecurity 提供的 AuthenticationEntryPoint 接口,使用 JSON 格式返回
![20305672182b1fc88bd442e59b07fb2c.png](https://img-blog.csdnimg.cn/img_convert/20305672182b1fc88bd442e59b07fb2c.png)
自定义 Token 验证过滤器
客户端登录成功时,后台会把生成的 token 返回给前端,之后客户端每次请求后台接口将会把这个 token 附在 header 头中传递给后台,后台会使用 JWT 工具类进行验证这个 token 是否有效,并把 JWT 中包含的信息解析成用户对象,加载至 SpringSecurity 中。
![b22c9ff65876b4ca06652440fa4ea6bb.png](https://img-blog.csdnimg.cn/img_convert/b22c9ff65876b4ca06652440fa4ea6bb.png)
JWT 工具类
![2cbf1ba52674e365230648d5cf90ce4f.png](https://img-blog.csdnimg.cn/img_convert/2cbf1ba52674e365230648d5cf90ce4f.png)
注册
在 configure 方法中将自定义的 jsonAuthenticationFilter 及 tokenAuthenticationFilter 注册进 SpringSecurity 的过滤器链中,并禁用 session。
![4cdfc7a7c47a3109a2ba3737f88a361b.png](https://img-blog.csdnimg.cn/img_convert/4cdfc7a7c47a3109a2ba3737f88a361b.png)
启动类
![ea0c24f105b96d34ee5441978d45a2c0.png](https://img-blog.csdnimg.cn/img_convert/ea0c24f105b96d34ee5441978d45a2c0.png)
验证结果
初始化数据
![7de95d7eb86450254ad0d4326e81e8f7.png](https://img-blog.csdnimg.cn/img_convert/7de95d7eb86450254ad0d4326e81e8f7.png)
源码地址
本章源码 : https://gitee.com/gongm_24/spring-boot-tutorial.git
结束语
与普通的 token 不同的是,JWT 作为 token 本身就已经包含了信息,而普通的 token 就只是一个字符串,需要用户信息就必须再去数据库或者其它中间件中进行加载,JWT 则可以省去这一步,这可以大幅度降低系统的 IO。
但是 JWT 也有自己的问题,那就是一旦生成,服务端将无法控制它,只要在有效期内就可以一直使用,所以 JWT 更适用于短期授权。