看了几个SpringSecurity+JWT的登陆demo,两个demo在一些细节实现上有一些不同,然后对于各个类和接口的关系比较模糊,就决定整理一下思路。

先简单的借用一下一位UP 三更草堂的图了解一下登陆大概的流程和用到的类,这个他讲的还比较清晰。

前后端分离项目登陆流程。

SpringSecurity工作流程(过滤器链)

登陆的后端实现:要实现登录验证主要需要实现上图过滤链的橙色方块部分的过滤器(UsernamePasswordAuthenticationFilter),这个过滤器的工作原理如下图。注意一开始受SpringSecurity过滤器链的思想影响会觉得下图的这几个类和接口也是过滤器链上的一个过滤器,实际上稍微他们之间的关系(调用、实现、继承)就知道这些都不是过滤器(废话,看名字就知道了,不过现在一定要非常明确这一点,否则在后续可能会感觉很混乱)。因此从过滤器链来看,下面的这些步骤在过滤器链中全部都属于橙色过滤器(UsernamePasswordAuthenticationFilter)这一步骤。

先不用细看这个图,或者先去看看三更草堂的几个视频大概了解一下流程就好。需要注意的是,后续还会设计很多很多乱七八糟的类的名字,这些类的名字都非常长,而且一定程度上有点相似,很容易搞混,因此给大家的建议是看这些名字的时候从后往前看。如上图,我们应该看到xxxxxFilter、xxxxxManager、 xxxxxProvider、 xxxxxService。当然还有一些关键词:Authentication、UserDetails等就ok了。
按照三更草堂的说法,明白流程之后知道哪个部分需要修改再修改就行了,他得出结论只用修改并实现上图的头部和尾部就可以了。实际上就是如此,这是最重要的,但是大家还记得这个图是什么吗?这个图只是一个登陆验证的流程,我们要整合SpringSecurity实际上还有一些步骤,而这些步骤实现的方法不同可能也会影响到登陆的实现方法不同。
上图虽然更美观清晰,但是小白无从下手,下面这个图是手画的实现登陆的一般思路。

我知道我画的丑字也丑,但是不急,看完下面的内容你应该很快就知道我画的是什么了。大概看一眼就OK。
那么整合SpringSecurity实现登陆的一般思路是怎么样的呢?
首先既然是前后端分离项目要实现登陆,我们就要写以一个接口对吧(虽然是废话,但是我觉得这很重要,是一切的开始),那我们就从Controller写起。内容就不赘述了,和其他接口一样,注入对应的Service,调用里面实现的login方法。那么问题来了,这个Service的要怎么编写呢?还是看最上面那张前后端登陆的原理图,看后端的部分,可以看总结出两个步骤:
1.验证
2.生成token并返回
这两个步骤分别对应这我们实现用的两个框架,验证我们使用SpringSecurity,负责生成解析token的是JWT。JWT不赘述了。如何使用SpringSecurity验证呢?SpringSecurity默认实现的就是使用上文提到的UsernamePasswordAuthenticationFilter(当整合了SpringSecurity之后就有一个默认的登陆页面,也即是UsernamePasswordAuthenticationFilter,SpringSecurity不仅仅提供了许多过接口,还提供了许多默认实现,我们需要的就是修改或者替换他们)。我们要怎么替换这个过滤器呢?我们只要接口调用自己实现的过滤器就好,那么问题来了,如何实现一个自己的过滤器呢?答案就是看看SpringSecurity默认的实现是怎么实现的我们照猫画虎即可。UsernamePasswordAuthenticationFilter实现登陆验证的流程就是上面那张红色的图,我们可以看到它实现验证使用的authenticate实际上是调用了xxxxManager的authenticate,而 xxxxManager的authenticate实际上是调用的xxxxxProvider的authenticate(实际上前面的xxxx补全了之后ProviderManager和DaoAuthenticationProvider,但是为了避免搞混还是用星号屏蔽一下方便看,而且其实图中画的ProviderManager调用了DaoAuthenticationProvider的authenticate是不严谨的,这个下文再说。)

说白了,在Service里我们只要调用xxxxxManager的authenticate方法就可以实现验证了,是不是很简单?调用了就写完了。想得美,虽然是调用就完事了,但是他要你提供一个参数Authentication。

这怎么办?这个是啥玩意?要怎么构造?不慌,于是不觉现在括号里打一个new就完事了,看看它有什么方法构造,我们有什么可以给它,万一有个无参构造就是免费的午餐了吗(谐音梗,好冷)。好,打一个new看看。

不得不说,有了IDE写代码简直不要太方便,一打个new他就自动列出了很多构造方法,而且我什么都没做它就帮我锁定了UsernamePasswordAuthenticationToken 。其实仔细想想也有道理啊,登录验证你当然要给她用户名密码去验证啊,不然拿什么验证呢哈哈哈。看着这个类名眼熟不?是不是好像在哪看过?对上面提到的过很多次的UsernamePasswordAuthenticationFilter长得很像有木有!事实上他们的关系八竿子打不着。都说了,看名字从后往前看,一个是Token,另一个是Filter,不要搞混了啊,不然实现的时候就糊里糊涂的。其实根据这个弹出来的构造方法我们可以知道这个Authentication只是一个接口,而xxxxxxToken 是他的实现类。使用IDEA的关系图功能就使得他们的关系一目了然了。

话说回来,这个东西要怎么构造呢?他一共有两个构造方法,一个两参数,一个三参数,因为我们看名字知道大概要给她username和password所以我们不难踩到他们对应这principal和credentials,英语好的老哥大概也能知道他们意思差不多,至于为什么不用三参数的构造的话一是因为我们没有authorities,也不知道怎么构造,事实上我们现在只有username和password。(实际上要是英文好一点的话可以看到官方告诉你了,xxxxxToken就用两参数的, 只有xxxxProvider和 xxxxxManger可以用三参数的)

好了,现在构造好了xxxxxToken之后他返回了一个Authentication对象,这个东西之前提过,是接口,使用了多态。先不管这个东西有什么用啊,反正到此为止我们已经完成了验证了。
那下一步是什么?构造JWT返回给前端呗。怎么构造?这个实际上拿什么来构造是自己决定的,一般就拿可以唯一表示用户的东西来构造,也就是username(也可以叫做userid)之类的东西。在这一步的时候大部分的实现都是通过Authentication的getPrincipal方法来获取用户,然后再拿id去构造的。事实上这个getPrincipal的值应该和之前构造xxxxxToken的时候一样的,理论上也可以直接用前端传过来的值去获取用户(甚至简单的项目中前端传回来的就是一个完整的用户)。不过还是按照大家写法来,这样看起来语义化一些。
至此其实整个登陆就已经完成了。但是还有一些地方没有实现。我们细看登陆流程的后面一部分。

UserDetailsService的默认实现类是什么?InMemoryUserDetailsManager!稍微翻译一下就是,这个是实现是在内存中管理用户的。回想我们刚真恶化SpringSecurity的时候就能有登录验证,这时候我们数据库里根本没有用户表,他们是怎么实现登陆的呢(默认用户是user,密码在控制台输出),答案就是他们没用数据库,而是就是在内存中生成的。这不符合我们实际项目的需求啊,所以要改!要换!怎么改怎么换?还是像上面一样,我们也照猫画虎的就可以了。在数据库建好表,生成model和mapper。需要注意的是SpringSecurity不是什么model都认的,它只认UserDetails,因此我们需要另外定义一个类来实现一个UserDetails,这个类用我们数据库生成的model来构造。实现的时候完成它所有重载就好。因为我做这个是时候是用于简单课设的登陆,所以很多方法是可以简化的。(也就是全部置为true哈哈哈)

这个类实现了之后我们就可以沟通了数据库和SpringSecurity了!但是我们还实现他们怎么认证,也就是还没有实现UserDetailsService,这个类只需要实现一个方法

而到这里的实现的时候我看的两份demo就出现了比较大的分歧了。一份就老老实实的实现了这个类。

而另一份则没有实现这个接口,而是在SecurityConfig中直接注册了一个UserDetailsService的Bean 到容器中。
两者的效果应该是一样的,但是总是让人有点不放心。毕竟这个方法返回的是Details而声明的是UserDetailService,而且 xxxxxxProvider 调用的是loadUserByUsername方法,而我们也没有实现 。这到底是怎么回事?让人有了一点看源码的欲望。首先看这个UserDetailsService接口:

也是个函数式接口,定义了loadUserByUsername方法,我们去看看它的实现类DaoAuthenticationProvider。你是这么想的就错了!UserDetailService没有实现类,需要我们用户定义,否则默认用的是InMemoryUserDetailsManager。而DaoAuthenticationProvider是调用了UserDetailService定义的loadUserByUsername方法。还记得这个xxxxxxProvider是啥不?我们验证的时候会调用这个东西的authenticate方法,正好去看看。
跳到DaoAuthenticationProvider,我们照例查看关系图,上图便是xxxxxxProvider的继承实现关系。

进去直接查找一下哪里用到了UserDetailService 然后就看到下面几个方法:


确确实实是调用了loadUserByUsername,但是这个UserDetailService是怎么来的?是外部传入的,这就有点突破我认知了,按照之前的想法应该是注入得到的。还有一件很奇怪的事情就是这个类里面没有authenticate方法!按照三更草堂的图来说这个类应该是有个authenticate方法的!这是为什么?仔细看一下那个关系图,会发现在这个类并不是第一个实现类,其父类同样是一个实现类,而运行的时候用的是最原始的接口类型的多态功能,所以很可能我们要找的答案就在父类AbstractUserDetailsAuthenticationProvider离。
跳进去看找到了authenticate

里面许多类还挺眼熟的,之前都有提到过,参数需要一个Authentication对象,我们传入的是UsernamePasswordAuthenticationToken ,它在这里还会判断一下它的实例…其中这个retrieveUser是我们在DaoAuthenticationProvider找到UserDetailService的那个方法,但是这里也没有提出UserDetailService是怎么设置的。


找到一个包装类看到他是怎么调用我们的实现的,但是对于那种方法为什么可以还是没有找到合理的解释。然而由于所学知识不多,眼看课设也快截止了,所以就不深究下去了,等以后知识储备够了或许就知道了。
不管怎么样,至此我们就几乎实现了登陆验证了。

然而我们里接入整个还需要编写一些SpringSecurity配置(实现WebSecurityConfigurerAdapter)。和实现一些类,并且注入(一般卸载Config里)。

关于加密解密的
PasswordEncoder

例如关于异常的
AccessDeniedHandler
AuthenticationEntryPoint

同时以上仅仅实现了第一次登陆的功能,后续登陆使用token的话我们可以在实现一个OncePerRequestFilter接口,放在首次登陆接口的前面。实现起来也不难。需要注意的是SecurityContextHolder的使用。

实现后全部加到Config类的config方法里

至此,SpringSecurity+JWT实现登陆验证的思路就清晰很多了,再看回故这幅图。

总结一下就是:其中蓝色的圈是我们要实现的,红色的字是service要实现的内容,其余黑色的框框是框架的调用逻辑,我们在实现的时候不需要理会。除此之外我们再配置并实现一个Config类(WebSecurityConfigurerAdapter)、关于异常处理的类、OncePerRequestFilter即可。

SpringSecurity+JWT实现登陆验证的思路(有一点点源码分析)相关推荐

  1. asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

    asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型 ...

  2. SpringBoot+JWT实现登陆token验证并存储用户信息

    基于Token的JWT认证 JWT:Json web token 是为了在网络应用环境间传递声明而执行的一种基于JSON传输格式的开放标准,可实现无状态.分布式的Web应用授权. 缺点:用户主动注销, ...

  3. 登陆验证发展史(cookie认证->session认证->token认证->JWT,单系统登陆->多系统单点登陆)

    登陆验证发展史有两条主线.在服务部署方式层面,早期的Web服务系统简单一般都是单系统,登陆的话就登陆这一个系统就好了,随着系统复杂性越来越高,一个大的系统往往由很多子系统组成,用户使用这个大系统时不可 ...

  4. SpringSecurity + JWT实现单点登录

    前面说了很多的理论知识,本文使用SpringSecurity + JWT来实现单点登录. 文章目录 什么是单点登陆 简单的运行机制 SpringSecurity整合JWT 认证思路分析 什么是单点登陆 ...

  5. Java项目:在线淘房系统(租房、购房)(java+SpringBoot+Redis+MySQL+Vue+SpringSecurity+JWT+ElasticSearch+WebSocket)

    源码获取:博客首页 "资源" 里下载! 该系统有三个角色,分别是:普通用户.房屋中介.管理员.普通用户的功能:浏览房屋信息.预约看房.和中介聊天.申请成为中介等等.房屋中介的功能: ...

  6. Security+jwt+验证码实现验证和授权

    微服务Security+jwt+验证码实现认证和授权 简要介绍 基本流程 核心代码 测试 简要介绍 本次博客采用Spring Security.jwt.验证码的形式实现登录验证,项目本身是一个前后端分 ...

  7. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  8. Springboot+Spring-Security+JWT 实现用户登录和权限认证

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的 ...

  9. SpringSecurity+JWT+OAuth2

    一个简洁的博客网站:http://lss-coding.top,欢迎大家来访 学习娱乐导航页:http://miss123.top/ 1. Spring Security 简介 1.1 概述 什么是安 ...

最新文章

  1. 计算机无法正常更新,无法完成更新正在撤销更改请不要关闭你的计算机如何修复...
  2. 如何在用例之间传递值_如何从0搭建自己的自动化测试体系
  3. 第37课 thinkphp5添加商品基本信息及通过前置钩子上传商品主图 模型事件(勾子函数)...
  4. 双极型三极管共集电极、共基极放大电路
  5. Python字典技巧一锅炖
  6. 2021年中国独立眼科超声系统市场趋势报告、技术动态创新及2027年市场预测
  7. OpenResty入门
  8. oa处理会签流程图_简易OA漫谈之工作流设计(四,怎么支持会签)
  9. 新概念英语(1-29)Come in, Amy.
  10. 微信公众号调用腾讯地图api
  11. 知乎上 40 个有趣回复,很精辟很提神
  12. C++设计一个长方形类
  13. 表白套路计算机公式,数学公式的超酷表白
  14. 北京职称计算机证书有效期,有关职称评审常见问题的解答(北京地区)
  15. 五个招数告诉你怎么让网站内容快速让百度收录
  16. rust提示游戏安全违规_RUST 游戏启动不了 每次都是 Rust Launcher Error: Loading Error - Start Service failed (1450)...
  17. TCP重连重试机制——记一次线上故障排查思考
  18. 《屏幕上的聪明决策》:4星。人类在手机/电脑上做选择的心理学研究的综述。不流畅的文本有助于理解和记忆,淘汰赛制可以有效降低选择后懊悔。...
  19. 【转】写给欲采访刘丁宁事件的媒体
  20. vue 加载image图片不显示解决

热门文章

  1. python机器学习之分类预测
  2. 用友财务共享系统付款单全流程图文演示
  3. WCF从理论到实践(3):八号当铺之黑色契约
  4. 第1115期AI100_机器学习日报(2017-10-07)
  5. 什么是Qt Widget?
  6. 附加码看不见的解决方法
  7. 2016CVPR目标检测论文简介
  8. 七、FFmpeg使用---AAC音频编译
  9. pytest系列——allure(三)之在测试报告中为测试用例添加附件(@allure.attach())
  10. 拟合算法(模型+代码)