上一章节中,我们介绍了大体的概念。这一章节将主要讲解oauth2.0 + jwt + spring security的开发知识
整体技术框架
- 认证中心(授权服务器): spring security oauth 2.1.2.RELEASE
- 鉴权框架: spring security 5.3.8.RELEASE
- 认证协议: oauth2.0
- 无状态令牌: jwt
认证中心框架选择
方案一: 升级现有的cas4.3至cas6.x版本:
总体上来说,6.x版本cas从功能上来说是满足我们的要求的,但是cas6.x有以下不足,让我最终放弃了使用它:
- 采用了gradle作为包管理器,而项目组现在只用maven,
- 6.x版本的cas将包拆分的过于细了,导致阅读与寻找源码并不方便,
- cas6.x官方并不推荐也不支持你去修改它的登录流程,自定义流程太麻烦了
综上,cas6.x适合那些登录流程简单较为固定,需要开箱即用的项目,不适合那些需要自定义修改很多内容的项目。对于企业应用来说,它推荐的使用方式是war包overlay部署方式,里面强塞了太多不需要的东西,太沉重了
还有一点,6.x版本的cas中文文档与博客极少,遇到问题只能看源码,比较痛苦。
方案二: 采用spring-security-oauth
虽然spring-security-oauth已经进入了维护阶段,后续官方将废弃该项目,现在spring官方已经将spring-security-oauth中的大部分功能(除了认证服务器)合并入了spring-security中统一管理,后续官方将为认证服务器单独推出一个全新的项目: spring-authorization-server。它目前还处于实验阶段,所以不敢采用。
而spring-security-oauth作为认证中心,虽然已经进入了停止更新状态,但官方依然会维护一段时间。并且作为一个很成熟的spring框架,它预留了很多的方法来供我们自定义,修改起来非常方便。并且设计上也很简单清爽,便于阅读,中文文档和博客也是很多的,所以我采用了这个框架作为我们的认证中心。
鉴权框架选择
shiro 和 spring-security在功能上都是很好的框架,但Shiro 最大的问题在于和 Spring 家族的产品进行整合的时候非常不便,对比之下,虽然spring-security相对Shiro会复杂很多,但基于 Spring Boot/Spring Cloud 的微服务项目基本上是原生支持spring-security的,对接起来非常方便。因此选择spring-security。
认证协议与令牌方案
oauth2.0+jwt, oauth2.0只是一种认证协议,具体的令牌方案目前最流行的就是自解析的jwt,很适合微服务方案。
问题: 假如微服务框架使用非自解析的令牌,相对于jwt有什么优势又有什么劣势呢?
==优势: 安全性更高,对令牌的控制更方便 劣势: 后端服务器压力大,一次认证流程中会有更多的http请求==
具体技术讲解
oauth2.0与jwt非常简单,这里就不仔细讲了。主要说一下认证中心与鉴权框架。
demo演示(几种模式及其参数解释)
这里推荐一个个人认为很好的demo, 地址如下
这个项目拉下来直接就可以跑,不需要配置任何数据库,按照readme中的提示一步步跑起来,然后主要要体会一下它的几种模式的调用和弄清楚它各个模式里面的参数都代表什么意思,代码原理倒不怎么用看
- 密码模式
密码模式请求的是 /oauth/token 这个接口,一共有五个参数:
参数名 | 位置 | 作用 | 说明 |
---|---|---|---|
username | Authorization中Type为Basic Auth的username选项 | 对应于oauth服务端中注册好的clientId | 无论是哪个客户端接入都必须提前注册clientId和clientSecret |
password | Authorization中Type为Basic Auth的password选项 | 对应于oauth服务端中注册好的clientSecret | 同上 |
username | Body中 | 用户名 | 无 |
password | Body中 | 密码 | 无 |
grant_type | Body中 | 授权类型 | 这个参数决定你调用的是哪种模式,密码模式为password |
- 授权码模式
第一步,浏览器访问这个地址 http://localhost:8001/oauth/authorize?response_type=code&scope=sever&client_id=yaohw&redirect_uri=http://www.baidu.com&state=0583 然后会重定向到登录页
参数名 | 解释 |
---|---|
response_type | 授权码模式固定为code |
scope | 请求的token的作用范围 |
client_id | 在oauth2认证中心注册的客户端Id |
redirect_uri | 登录成功后的重定向地址,该地址必须与在oauth2认证中心注册的重定向地址完全一致 |
state | 自定义参数,忽略 |
第二步,在页面输入用户名和密码,就会带着授权码?code=xxx跳转到上面哪个地址,将授权码拷贝下来
第三步,post访问http://localhost:8001/oauth/token,参数如下
访问/oauth/token时候,请求头Authorization中都必须配置Type为Basic Auth,然后将clientId和clientSecret设入,后面就不讲解这个了
参数名 | 解释 |
---|---|
grant_type | 这里对应获取token的模式,授权码模式必须为authorization_code |
code | 上一步获取的授权码 |
redirect_uri | 登录成功后的重定向地址,该地址必须与在oauth2认证中心注册的重定向地址完全一致 |
下面三种模式就不多说了,你们可以仔细观察下调用的几个接口的区别,可以发现以下几点区别
只要是前端页面跳转,都是调用http://localhost:8001/oauth/authorize?response_type=这个地址,并且是通过response_type这个参数来区分模式
而如果是后端接口调用,则是通过http://localhost:8001/oauth/token这个接口来调用,并且通过grant_type这个字段来区分模式,并且一定带有clientId和clientSecret
- 自定义手机验证码模式(这个可以忽略,需要搞redis,是作者自定义的,不嫌麻烦也可以试下)
- 简化模式
- 刷token模式
从接口开始走一遍各个模式的流程
因为上面我们发现oauth2.0的四种模式,其实就对应着两个地址/token和/authorize.因此接下来,我们就过一下着两个url对应的入口TokenEndpoint和AuthorizationEndpoint
AuthorizationEndpoint
对应认证码模式和纯前端的简化模式,具体实现类为org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
总体入口在122行的authorize方法这里,下面我会把没用的代码都删掉,通过注释带大家看一遍这个方法
1 | @RequestMapping(value = "/oauth/authorize") |
总结一下,这个方法对登录和未登录的访问会进行以下两种处理流程:
- 假如你是未登录来访问这个接口,那么会抛出异常,被ExceptionTranslationFilter捕获,调用handleSpringSecurityException方法,进入sendStartAuthentication方法,假如你配置的是重定向地址,他这里应该就会找到这个LoginUrlAuthenticationEntryPoint,给个重定向到登录页,让你登录。
- 当你登录server之后,再进这里就是已登录状态了,就直接过去了,然后给你生成code重定向走
TokenEndpoint
对应密码模式和纯后端的client_credentials模式以及拿着code请求token的认证码模式的第二步,具体实现类为org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
总体入口在87行的postAccessToken方法这里,下面我会把没用的代码都删掉,通过注释带大家看一遍这个方法
1 | @RequestMapping(value = "/oauth/token", method=RequestMethod.POST) |
总结一下,token模式,假如你未登录来访问这个接口,也会抛出异常。这个接口的登录是通过配置认证头来实现的,username配置clientId,password配置clientSecret.然后在UsernamePassword*Filter中捕获并注入principal。
这里如果登录无误,就会进入到最重要的方法里,132行的grant中,然后进行生成token操作
然后看一下重要的组件
上一章看完了,那么核心问题转化为了,spring security是怎么生成token的。而生成token的核心方法对应于org.springframework.security.oauth2.provider.TokenGranter这个接口的grant方法.接下来让我们看下这个方法,同时再去了解下spring security的核心组件吧。
TokenGranter
话不多说,直接看代码
首先看一下继承关系:
在上面的token模式中,grant方法默认会进入到AbstractTokenGranter的grant方法中,而假如我们需要自定义认证模式,一般做法也是去继承这个抽象的父类,比如MobileCodeTokenGranter
因此直接看下这个自定义的MobileCodeTokenGranter的代码吧:
1 | /** |
因此认证的核心方法最后交给了AuthenticationManager这个类,而这个类又是通过与其他的组件交互,最后完成认证的:
下面的内容建议直接看该博客:https://www.jianshu.com/p/7b87ec108405
简单的说就是,spring security通过不同的grantType,将颁发token的操作委托给了TokenGranter。
而TokenGranter通过将不同的登录参数组装为不同的token,然后将token委托给AuthenticationMnager来处理。
AuthenticationManager根据不同的token类型,通过调用AuthenticationProvider接口的support方法来决定,将这个token委托给哪个provider处理,最后的主要校验逻辑都是由provider来实现的。
spring security oauth 认证服务器
说到底oauth-server其实就是在spring-security的基础上增加了两个入口和一系列过滤器还有上面这些认证组件,两个入口和这些认证组件上面已经分析过了。剩下的核心其实还是spring-security,通过这一系列的过滤器,spring 完成了一系列鉴权与跳转操作,同时为我们预留了很多接口,方便我们自定义
spring security 本身并不复杂,它的核心原理可以用这张图和三局话概括:
- 整个框架的核心是一个过滤器,这个过滤器名字叫springSecurityFilterChain类型是FilterChainProxy
- 核心过滤器里面是过滤器链(列表),过滤器链的每个元素都是一组URL对应一组过滤器
- WebSecurity用来创建FilterChainProxy过滤器,HttpSecurity用来创建过滤器链的每个元素。
如果想仔细了解,推荐这个博客,上面三句话也是来自这个博客的: https://blog.csdn.net/zimou5581/article/details/102457672
另外spring security提供了非常多的过滤器,如果想要了解的话,可以看这个博客:https://cloud.tencent.com/developer/article/1551517
内网的auth-server 和 client 的项目结构和改造思路,以及如何配置使用
参见内网文档 以及 代码注释