写在前面
在前一篇文章《springBoot整合spring security实现权限管理(单体应用版)–筑基初期》当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与权限管理。
本文涉及的权限管理模型是基于资源的动态权限管理。数据库设计的表有 user 、role、user_role、permission、role_permission。
单点登录当中,关于访问者信息的存储有多种解决方案。如将其以key-value的形式存储于redis数据库中,访问者令牌中存放key。校验用户身份时,凭借访问者令牌中的key去redis中找value,没找到则返回“令牌已过期”,让访问者去(重新)认证。本文中的demo,是将访问者信息加密后存于token中返回给访问者,访问者携带令牌去访问服务时,服务提供者直接解密校验token即可。两种实现各有优缺点。大家也可以尝试着将本文中的demo的访问者信息存储改造成存在redis中的方式。文末提供完整的代码及sql脚本下载地址。
在进入正式步骤之前,我们需要了解以下知识点。
单点登录SSO
单点登录也称分布式认证,指的是在有多个系统的项目中,用户经过一次认证,即可访问该项目下彼此相互信任的系统。
单点登录流程
给大家画了个流程图
关于JWT
jwt,全称JSON Web Token,是一款出色的分布式身份校验方案。
jwt由三个部分组成
- 头部:主要设置一些规范信息,签名部分的编码格式就在头部中声明。
- 有效载荷:token中存放有效信息的部分,比如用户名,用户角色,过期时间等,但不适合放诸如密码等敏感数据,会造成泄露。
- 签名:将头部与载荷分别采用base64编码后,用“.”相连,再加入盐,最后使用头部声明的编码类型进行编码,就得到了签名。
jwt生成的Token安全性分析
想要使得token不被伪造,就要确保签名不被篡改。然而,其签名的头部和有效载荷使用base64编码,这与明文无异。因此,我们只能在盐上做手脚了。我们对盐进行非对称加密后,在将token发放给用户。
RSA非对称加密
- 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端 。
* 公钥加密:只有私钥才能解密,一般公钥具有多个拷贝
* 私钥加密:只有公钥才能解密,一般私钥只有一份
- 优缺点:
* 优点:安全、难以破解
* 缺点:耗时,但是为了安全,这是可以接受的
SpringSecurity+JWT+RSA分布式认证思路分析
通过之前的学习,我们知道了spring security主要是基于过滤器链来做认证的,因此,如何打造我们的单点登录,突破口就在于spring security中的认证过滤器。
用户认证
在分布式项目当中,现在大多数都是前后端分离架构设计的,因此,我们需要能够接收POST请求的认证参数,而不是传统的表单提交。因此,我们需要修改修
改UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法,让其能够接收请求体。
关于spring security的认证流程分析,大家可以参考我上一篇文章《Spring Security认证流程分析–练气后期》。
另外,默认情况下,successfulAuthentication 方法在通过认证后,直接将认证信息放到服务器的session当中就ok了。而我们分布式应用当中,前后端分离,禁用了session。因此,我们需要在认证通过后生成token(载荷内具有验证用户身份必要的信息)返回给用户。
身份校验
默认情况下,BasicAuthenticationFilter过滤器中doFilterInternal方法校验用户是否登录,就是看session中是否有用户信息。在分布式应用当中,我们要修改为,验证用户携带的token是否合法,并解析出用户信息,交给SpringSecurity,以便于后续的授权功能可以正常使用。
实现步骤
(默认大家一已经创建好了数据库)
第一步:创建一个springBoot的project
这个父工程主要做依赖的版本管理。
其pom.xml文件如下
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
第二步:创建三个子模块
其中,common模块作为公共模块存在,提供基础服务,包括token的生成、rsa加密密钥的生成与使用、Json序列化与反序列化。
authentication-service模块提供单点登录服务(用户认证及授权)。
product-service模块模拟一个子系统。它主要负责提供接口调用和校验用户身份。
创建common模块模块
#####修改pom.xml,添加jwt、json等依赖
pom.xml
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
创建一个JSON工具类
1 | java复制代码**json工具类 |
创建RSA加密工具类,并生成公钥和密钥文件
RsaUtils.java
1 | java复制代码/**RSA非对称加密工具类 |
生成私钥和公钥两个文件
1 | java复制代码/** |
私钥文件一定要保护好!!!
私钥文件一定要保护好!!!
私钥文件一定要保护好!!!
(重要的事情说三遍!!!)
1 | shell复制代码##### 创建token有效载荷实体类和JWT工具类 |
1 | java复制代码/**为了方便后期获取token中的用户信息, |
JwtUtils
1 | java复制代码/**token工具类 |
写完common模块后,将其打包安装,后面的两个服务都需要引用。
创建认证服务模块authentication-service
认证服务模块的关键点在于自定义用户认证过滤器和用户校验过滤器,并将其加载到spring security的过滤器链中,替代掉默认的。
1 | shell复制代码##### 修改pom.xml文件,添加相关依赖 |
pom.xml
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
这个模块添加的依赖主要是springBoot整合spring security的相关依赖以及数据库相关的依赖,当然还有我们的common模块。
修改application.yml文件
这一步主要是设置数据库连接的信息以及公钥、私钥的位置信息
1 | yaml复制代码server: |
配置解析公钥和私钥
1 | java复制代码**解析公钥和私钥的配置类 |
修改启动类,添加token加密解析的配置和mapper扫描
1 | java复制代码/** |
创建用户登录对象UserLoginVO
我们将用户登录的请求参数封装到一个实体类当中,而不使用与数据库表对应的UserTO。
1 | Java复制代码/**用户登录请求参数对象 |
创建用户凭证对象UserAuthVO
这个对象主要用于存储访问者认证成功后,其在token中的信息。这里我们是不存储密码等敏感数据的。
1 | java复制代码/**用户凭证对象 |
创建自定义认证过滤器
1 | Java复制代码/**自定义认证过滤器 |
到了这一步,你或许会开始觉得难以理解,这需要你稍微了解spring security的认证流程。可以阅读我之前的文章《Spring Security认证流程分析–练气后期》。
创建自定义校验过滤器
1 | java复制代码/**自定义身份验证器 |
编写spring security的配置类
这一步主要是是完成对spring security的配置。唯一和单体版应用集成spring’security不同的是,在这一步需要加入我们自定义的用户认证和用户校验的过滤器,还有就是禁用session。
1 | java复制代码/**spring security配置类 |
添加对GrantedAuthority类型的自定义反序列化工具
因为我们的权限信息是加密存储于token中的,因此要对authorities进行序列化与反序列化,然后由于jackson并不支持对其进行反序列化,因此需要我们自己去做。
1 | java复制代码** |
在UserAuthVO上标记
1 | java复制代码/**用户凭证对象 |
实现UserDetailsService接口
实现loadUserByUsername方法,修改认证信息获取方式为:从数据库中获取权限信息。
1 | java复制代码/** |
**提示:**关于用户、角色、权限的数据库操作及其实体类到这里就省略了,不影响大家理解,当然,文末提供了完整的代码下载地址。
自定义401和403异常处理
Spring Security 中的异常主要分为两大类:一类是认证异常,另一类是授权相关的异常。并且,其抛出异常的地方是在过滤器链中,如果你使用@ControllerAdvice是没有办法处理的。
当然,像spring security这么优秀的框架,当然考虑到了这个问题。
spring security当中的HttpSecurity提供的exceptionHandling() 方法用来提供异常处理。该方法构造出 ExceptionHandlingConfigurer异常处理配置类。
然后该类呢有提供了两个接口用于我们自定义异常处理:
- AuthenticationEntryPoint 该类用来统一处理 AuthenticationException异常(403异常)
- AccessDeniedHandler 该类用来统一处理 AccessDeniedException异常(401异常)
MyAuthenticationEntryPoint.java
1 | Java复制代码/**401异常处理 |
MyAccessDeniedHandler.java
1 | java复制代码/**403异常处理 |
将这两个类添加到spring security的配置当中
1 | Java复制代码/**spring security配置类 |
到这一步大家就可以运行启动类先进行测试一下。在本文当中就先将product-service模块也实现了再集中测试
创建子系统模块product-service
修改pom.xml文件
这一步和我们创建认证服务时相差无几。
1 | xml复制代码<?xml version="1.0" encoding="UTF-8"?> |
修改application.yml配置文件
这里主要是配置数据库信息和加入公钥的地址信息
1 | yaml复制代码server: |
创建读取公钥的配置类
1 | Java复制代码/**读取公钥配置类 |
修改启动类
这一步和创建认证服务器时一样,如要是加入公钥配置和mapper扫描
1 | java复制代码/** |
复制
这一步主要是将UserAuthVo、自定义校验器、自定义异常处理器和自定义反序列化器从认证服务模块复制过来。(之所以不放入到公共模块common中是因为。不想直接在common模块中引入springBoot整合spring security的依赖)
创建子模块spring security配置类
这里也只需要在认证服务模块的配置上修改即可,去掉自定义认证过滤器的内容。资源模块只负责校验,不做认证。
创建一个测试接口
1 | java复制代码/** |
第三步:启动项目,进行测试
登录(认证)操作
登录成功返回消息提示
并且可以在请求头中看到token
登陆失败提示”用户名或密码错误”
访问资源
携带令牌访问资源,且具备权限、令牌未过期
携带token访问资源。但是没有权限
未携带token访问(未登录、未经过认证)
携带过期令牌访问资源
写在最后
springBoot整合security实现权限管理与认证分布式版(前后端分离版)的的核心在于三个问题
- 禁用了session,用户信息保存在哪?
- 如何实现对访问者的认证,或者说是根据token去认证访问者?
- 如何实现对访问者的校验,或者说是根据token去校验访问者身份?
基本上我们解决了上面三个问题之后,springBoot整合spring security实现前后端分离(分布式)场景下的权限管理与认证问题我们就可以说是基本解决了。
**代码以及sql脚本下载方式:**微信搜索关注公众号【Java开发实践】,回复20200904即可得到下载链接。
本文转载自: 掘金