SpringSecurity+JWT认证流程解析
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity+JWT认证流程解析相关的知识,希望对你有一定的参考价值。
参考技术A 本文适合: 对Spring Security有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,对SpringSecurity有兴趣的也可以当作你们的入门教程,示例代码中也有很多注释。
大家在做系统的时候,一般做的第一个模块就是 认证与授权 模块,因为这是一个系统的入口,也是一个系统最重要最基础的一环,在认证与授权服务设计搭建好了之后,剩下的模块才得以安全访问。
市面上一般做认证授权的框架就是shiro和Spring Security,也有大部分公司选择自己研制。出于之前看过很多Spring Security的入门教程,但都觉得讲的不是太好,所以我这两天在自己鼓捣Spring Security的时候萌生了分享一下的想法,希望可以帮助到有兴趣的人。
Spring Security框架我们主要用它就是解决一个认证授权功能,所以我的文章主要会分为两部分:
我会为大家用一个Spring Security + JWT + 缓存的一个demo来展现我要讲的东西,毕竟脑子的东西要体现在具体事物上才可以更直观的让大家去了解去认识。
学习一件新事物的时候,我推荐使用自顶向下的学习方法,这样可以更好的认识新事物,而不是盲人摸象。
注 :只涉及到用户认证授权不涉及oauth2之类的第三方授权。
想上手 Spring Security 一定要先了解它的工作流程,因为它不像工具包一样,拿来即用,必须要对它有一定的了解,再根据它的用法进行自定义操作。
我们可以先来看看它的工作流程:
在Spring Security的官方文档上有这么一句话:
Spring Security 的web基础是Filters。
这句话展示了Spring Security的设计思想: 即通过一层层的Filters来对web请求做处理。
放到真实的Spring Security中,用文字表述的话可以这样说:
一个web请求会经过一条过滤器链,在经过过滤器链的过程中会完成认证与授权,如果中间发现这条请求未认证或者未授权,会根据被保护API的权限去抛出异常,然后由异常处理器去处理这些异常。
用图片表述的话可以这样画,这是我在百度找到的一张图片:
如上图,一个请求想要访问到API就会以从左到右的形式经过蓝线框框里面的过滤器,其中绿色部分是我们本篇主要讲的负责认证的过滤器,蓝色部分负责异常处理,橙色部分则是负责授权。
图中的这两个绿色过滤器我们今天不会去说,因为这是Spring Security对form表单认证和Basic认证内置的两个Filter,而我们的demo是JWT认证方式所以用不上。
如果你用过Spring Security就应该知道配置中有两个叫formLogin和httpBasic的配置项,在配置中打开了它俩就对应着打开了上面的过滤器。
换言之,你配置了这两种认证方式,过滤器链中才会加入它们,否则它们是不会被加到过滤器链中去的。
因为Spring Security自带的过滤器中是没有针对JWT这种认证方式的,所以我们的demo中会 写一个JWT的认证过滤器,然后放在绿色的位置进行认证工作。
知道了Spring Security的大致工作流程之后,我们还需要知道一些非常重要的概念也可以说是组件:
上下文对象,认证后的数据就放在这里面,接口定义如下:
这个接口里面只有两个方法,其主要作用就是get or set Authentication。
可以说是SecurityContext的工具类,用于get or set or clear SecurityContext,默认会把数据都存储到当前线程中。
这几个方法效果如下:
Authentication只是定义了一种在SpringSecurity进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。
AuthenticationManager定义了一个认证方法,它将一个未认证的Authentication传入,返回一个已认证的Authentication,默认使用的实现类为:ProviderManager。
接下来大家可以构思一下如何将这四个部分,串联起来,构成Spring Security进行认证的流程:
1. 先是一个请求带着身份信息进来
2. 经过AuthenticationManager的认证,
3. 再通过SecurityContextHolder获取SecurityContext,
4. 最后将认证后的信息放入到SecurityContext。
真正开始讲诉我们的认证代码之前,我们首先需要导入必要的依赖,数据库相关的依赖可以自行选择什么JDBC框架,我这里用的是国人二次开发的myabtis-plus。
接着,我们需要定义几个必须的组件。
由于我用的Spring-Boot是2.X所以必须要我们自己定义一个加密器:
这个Bean是不必可少的,Spring Security在认证操作时会使用我们定义的这个加密器,如果没有则会出现异常。
实现UserDetailsService的抽象方法并返回一个 UserDetails 对象,认证过程中SpringSecurity会调用这个方法访问数据库进行对用户的搜索,逻辑什么都可以自定义,无论是从数据库中还是从缓存中,但是我们需要将我们查询出来的用户信息和权限信息组装成一个 UserDetails 返回。
UserDetails 也是一个定义了数据形式的接口,用于保存我们从数据库中查出来的数据,其功能主要是验证账号状态和获取权限,具体实现可以查阅我仓库的代码。
由于我们是JWT的认证模式,所以我们也需要一个帮我们操作Token的工具类,一般来说它具有以下三个方法就够了:
在下文我的代码里面,JwtProvider充当了Token工具类的角色,具体实现可以查阅我仓库的代码。
有了前面的讲解之后,大家应该都知道用SpringSecurity做JWT认证需要我们自己写一个过滤器来做JWT的校验,然后将这个过滤器放到绿色部分。
在我们编写这个过滤器之前,我们还需要进行一个认证操作,因为我们要先访问认证接口拿到token,才能把token放到请求头上,进行接下来请求。
如果你不太明白,不要紧,先接着往下看我会在这节结束再次梳理一下。
访问一个系统,一般最先访问的是认证方法,这里我写了最简略的认证需要的几个步骤,因为实际系统中我们还要写登录记录啊,前台密码解密啊这些操作。
这里一共五个步骤,大概只有前四步是比较陌生的:
这样的话就算完成了,感觉上很简单,因为主要认证操作都会由authenticationManager.authenticate()帮我们完成。
接下来我们可以看看源码,从中窥得Spring Security是如何帮我们做这个认证的(省略了一部分):
看了源码之后你会发现和我们平常写的一样,其主要逻辑也是查数据库然后对比密码。
登录之后效果如下:
我们返回token之后,下次请求其他API的时候就要在请求头中带上这个token,都按照JWT的标准来做就可以。
有了token之后,我们要把过滤器放在过滤器链中,用于解析token,因为我们没有session,所以我们每次去辨别这是哪个用户的请求的时候,都是根据请求中的token来解析出来当前是哪个用户。
所以我们需要一个过滤器去拦截所有请求,前文我们也说过,这个过滤器我们会放在绿色部分用来替代UsernamePasswordAuthenticationFilter,所以我们新建一个JwtAuthenticationTokenFilter,然后将它注册为Bean,并在编写配置文件的时候需要加上这个:
addFilterBefore的语义是添加一个Filter到XXXFilter之前,放在这里就是把JwtAuthenticationTokenFilter放在UsernamePasswordAuthenticationFilter之前,因为filter的执行也是有顺序的,我们必须要把我们的filter放在过滤器链中绿色的部分才会起到自动认证的效果。
接下来我们可以看看JwtAuthenticationTokenFilter的具体实现了:
代码里步骤虽然说的很详细了,但是可能因为代码过长不利于阅读,我还是简单说说,也可以直接去仓库查看源码:
这样的话,每一个带有正确token的请求进来之后,都会找到它的账号信息,并放在上下文对象中,我们可以使用SecurityContextHolder很方便的拿到上下文对象中的Authentication对象。
完成之后,启动我们的demo,可以看到过滤器链中有以下过滤器,其中我们自定义的是第5个:
就酱,我们登录完了之后获取到的账号信息与角色信息我们都会放到缓存中,当带着token的请求来到时,我们就把它从缓存中拿出来,再次放到上下文对象中去。
结合认证方法,我们的逻辑链就变成了:
登录拿到token请求带上tokenJWT过滤器拦截校验token将从缓存中查出来的对象放到上下文中
这样之后,我们认证的逻辑就算完成了。
认证和JWT过滤器完成后,这个JWT的项目其实就可以跑起来了,可以实现我们想要的效果,如果想让程序更健壮,我们还需要再加一些辅助功能,让代码更友好。
当用户未登录或者token解析失败时会触发这个处理器,返回一个非法访问的结果。
当用户本身权限不满足所访问API需要的权限时,触发这个处理器,返回一个权限不足的结果。
用户退出一般就是清除掉上下文对象和缓存就行了,你也可以做一下附加操作,这两步是必须的。
JWT的项目token刷新也是必不可少的,这里刷新token的主要方法放在了token工具类里面,刷新完了把缓存重载一遍就行了,因为缓存是有有效期的,重新put可以重置失效时间。
这篇文我从上周日就开始构思了,为了能讲的老妪能解,修修改改了几遍才发出来。
作者:和耳朵
链接:https://juejin.cn/post/6846687598442708999
以上是关于SpringSecurity+JWT认证流程解析的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security 解析 —— 基于JWT的单点登陆(SSO)开发及原理解析
spring boot整合jwt 实现前后端分离登录认证及授权
SpringSecurity - 整合JWT使用 Token 认证授权