文章
分布式系统认证解决方案SpringSecurityOAuth2.0(一)认证授权
分布式系统认证解决方案SpringSecurityOAuth2.0(二)分布式系统认证流程分析与实现
分布式系统认证解决方案SpringSecurityOAuth2.0(三)资源服务器使用Redis令牌、JWT令牌认证及RSA非对称加密算法
分布式系统认证解决方案SpringSecurityOAuth2.0(四)整合网关认证授权
一、简介
最终实现目标:
OAuth(开放授权)2.0是一个开放标准,允许用户
授权第三方应用
访问他们存储在另外的服务提供者
上的信息,而不需要将用户名和密码提供给第三方应用。比如:用户(资源拥有者)授权 王者荣耀
(第三方应用,也称为客户端)允许访问用户在微信
(资源服务器)的个人信息,从而登录王者荣耀。
OAuth协议目前已经发展到了2.0版本,1.0版本过于复杂,2.0版本已经得到广泛应用。
1.1 OAuth2.0协议角色
- 客户端,需要通过资源拥有者的授权去请求资源服务器。
- 资源拥有者,通常为用户,即该资源拥有者。
- 授权服务器,也称认证服务器,认证成功后会给客户端发送令牌,作为客户端访问资源服务器的凭证,如微信的认证服务器。
- 资源服务器,存储资源的服务器,例如微信存储的用户信息。
1.2 SpringSecurityOAuth2.0
SpringSecurityOAuth2
是对OAuth2
的一种实现,并且跟SpringSecurity相辅相成,与SpringCloud体系的集成也十分的方便,我们使用它来设计分布式认证授权解决方案。
OAuth2.0
服务提供方涵盖两个服务,即授权服务
(Authorization Server,也叫认证服务)和资源服务
(Resource Server),使用SpringSecurityOAuth2
的时候可以在同一个应用程序中来实现,也可以选择建立使用同一个授权服务的多个资源服务。
授权服务 AuthorizationServer
授权服务(Authorization Server)应包含对接入端以及登入用户的合法性进行验证并颁发Token等功能,对令牌的请求端点由SpringMVC控制器进行实现,下面是配置一个认证服务必须要实现的endpoints:
Authorizationendpoint 服务于认证请求。默认 URL:/oauth/authorize。
TokenEndpoint 服务于访问令牌的请求。默认 URL:/oauth/token 。资源服务 ResourceServer
资源服务(Resource Server),应包含对资源的保护功能,对非法请求进行拦截,对请求中token进行解析鉴权等。
OAuth2AuthenticationProcessingFilter
用来对请求给出的身份令牌解析鉴权。
二、认证流程
客户端
请求授权服务
进行授权认证。- 授权认证通过后由
授权服务
颁发令牌Token
。 客户端
携带令牌Token
请求资源服务
。资源服务
校验令牌
的合法性,合法即返回资源信息。
但并不是所有的客户端都可以访问认证服务器,认证服务器会给客户端提供一个身份:client_id
(客户端标识)和client_secret
(客户端秘钥)。
所以认证服务器
需要对资源拥有者
和客户端
两个角色进行校验。需要确认:
- 是否是本人授权
- 是否是合法的客户端。
以授权码模式为例,以下是授权码模式
认证流程:
三、授权服务器——代码实现
授权服务器的作用就是对客户端进行认证授权,让其有资格访问用户的资源服务。
3.1 主要依赖
<!-- SpringSecurity 权限 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
</dependency>
<!--spring-security-oauth2-autoconfigure-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
3.2 AuthorizationServer
新建AuthorizationServer
类需要继承AuthorizationServerConfigurerAdapter
。
添加@Configuration
和@EnableAuthorizationServer
注解。
实现AuthorizationServerConfigurerAdapter
类的三个方法:
3.2.1 配置客户端详情信息
配置客户端详情信息,在此处配置的客户端详细信息,来申请授权或者申请令牌的客户端才能被识别。
resourceIds
资源列表和scope
允许授权的范围都是自己定义的标识。autoApprove
自动批准设置为false会跳转到手动授权页面,有用户决定是否授权。- authorizedGrantTypes 表示该客户端的授权类型:包括
authorization_code
授权码模式、password
密码模式、client_credentials
客户端模式、implicit
隐式授权模式以及refresh_token
更新token。
注意:只有客户端在这里配置的授权类型,在申请令牌的时候才能使用对应的授权类型,比如:如果在配置c1客户端的客户端详细信息的时候没有配置密码授权模式,则在申请令牌的时候就不能使用密码模式。
3.2.2 配置令牌的服务端点及令牌管理服务
首先配置令牌管理服务
,包括客户端详情服务、令牌存储策略等配置。
- 配置客户端详情服务是为了识别客户端的身份,因为并不是随便一个客户端都能来申请授权或者申请令牌,必须是配置到客户端详细服务中的客户端才可以。
- 配置令牌存储策略则是配置令牌是使用哪种方式存储。
然后需要配置令牌的服务端点包括认证管理器
、授权码服务
、令牌管理服务
以及允许的令牌服务端口的请求方法
为POST请求:
- 认证管理器:认证用户信息。
认证管理器AuthorizationManager
是在SpringSecurity的配置类中的,
关于SpringSecurity的认证可以参考上一章:https://blog.csdn.net/DreamsArchitects/article/details/119777733
-
授权码服务:设置授权码模式的授权码如何存储。
-
令牌管理服务,将我们配置的令牌管理服务配置到令牌服务端点中。
令牌服务端点就是客户端来请求授权或者来申请令牌的地址:
configure(ClientDetailsServiceConfigurer clients) clients的pathMapping()方法用来配置端点的URL链接,
//这是框架默认的地址:
/oauth/authorize 授权端点
/oauth/token 令牌端点
/oauth/confirm_access 用户确认授权提交端点
/oauth/error 授权服务错误信息端点
/oauth/check_token 用于资源服务访问的令牌解析端点
/oauth/token_key 提供公有秘钥的端点,比如使用JWT令牌 采用RSA非对称加密方式
3.2.3 配置令牌服务端点的安全约束
上一步中我们配置了令牌服务端点,但是这些服务端点不是随便都可以请求的,需要配置一些安全约束:
3.2.4 总结
这里的配置可能有点乱,但是是有逻辑的:
- 既然要完成认证,它首先需要知道客户端信息从哪读取,所以需要配置客户端详细信息。
- 既然要颁发token,就必须定义token的相关endpoint,以及token如何存取及客户端支持哪些类型的token。
- 既然暴露了这些endpoint,就需要对这些endpoint加上安全约束。
三、授权服务器——测试
3.1 授权码模式示例
申请授权
在浏览器访问:http://localhost:8081/oauth2/oauth/authorize?client_id=c1&client_secret=secret&response_type=code&scope=all&redirect_uri=http://www.baidu.com
,申请用户授权。
然后跳转授权服务
授权页面,此时需要登录认证授权服务。
客户端ID标识 | 客户端秘钥 | 授权模式 | 权限范围 | 重定向URL |
client_id | client_secret | response_type | scope | redirect_uri |
c1 | secret | code | all | http://www.baidu.com |
如下图所示:
登录认证成功后跳转授权页面,如下图所示:
同意授权
点击同意授权浏览器跳转重定向页面:https://www.baidu.com/?code=jUShMQ
,
在重定向的路径中返回授权码信息,如下图所示,code
即为授权服务返回的授权码
。
拒绝授权
页面跳转至重定向页面,在重定向路径中显示用户拒绝授权信息:https://www.baidu.com/?error=access_denied&error_description=User%20denied%20access
,如下图所示:
申请令牌
在上一步用户同意授权后,客户端
获得授权服务器
返回的授权码
,使用授权码去授权服务器
申请令牌access_token
,grant_type
指定为authorization_code
(授权码模式)
请求为POST
方式。
客户端ID标识 | 客户端秘钥 | 授权模式 | 授权码 | 重定向URL |
client_id | client_secret | grant_type | code | redirect_uri |
c1 | secret | authorization_code | HnqLzS | http://www.baidu.com |
返回的信息包含:
access_token
为令牌、
token_type
为令牌类型、
refresh_token
为更新令牌的token,更新令牌时用到、
expires_in
为令牌过期时间,单位为秒、
scope
为授权范围。
3.2 密码模式示例
使用密码模式的话就不需要申请授权了,直接申请令牌,需要指定grant_type
为password
,携带username
和password
字段。
申请令牌
3.3 简单授权模式示例
申请授权
简单模式就是在申请授权的时候,如果授权服务器认证成功,则直接返回令牌:
简单模式请求申请授权接口:http://localhost:8081/oauth2/oauth/authorize?client_id=c1&client_secret=secret&response_type=token&scope=all&redirect_uri=http://www.baidu.com
。
需要将response_type
指定为token
。
同意授权后直接返回令牌
令牌access_token
包含在重定向的地址中:https://www.baidu.com/#access_token=fd531d73-7030-401f-b307-99301888feaf&token_type=bearer&expires_in=7199
。
3.4 客户端授权模式
申请令牌
客户端授权模式只需要客户端的ID和客户端秘钥进行申请令牌:
需要将grant_type
改为client_credentials
。
客户端ID标识 | 客户端秘钥 | 授权模式 |
client_id | client_secret | grant_type |
c1 | secret | client_credentials |
四、资源服务器——代码实现
4.1 OrderController定义访问的资源服务的请求
OrderController
定义访问的资源服务的请求:/order/test
4.2 ResourceServer资源服务配置
ResourceServer
资源服务配置:
@EnableResourceServer
注解到一个@Configuration
配置类上,并且需要继承ResourceServerConfigurerAdapter
类重写其中方法:
(@EnableResourceServer
注解自动增加了一个类型为OAuth2AuthorizationProcessingFilter
的过滤器链)
***ResourceServerSecurityConfigurer***中主要包括:
- tokenService:ResourceServerTokenService类的实例,用来实现令牌服务。
- tokenStore:TokenStore类的实例,指定令牌如何访问,与tokenServices配置可选。
- resourceId:这个资源服务的ID,属性可选,推荐设置并在授权服务中验证。
- 其他拓展属性例如tokenExtractor令牌提取器用来提取请求中的令牌。
验证token:
ResourceServerTokenService是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序,可以使用DefaultTokenService,这样就不用考虑实现所有必要的接口的一致性问题。如果资源服务器是分开的,那么就必须确保能够有匹配授权服务提供的ResourceServerTokenService,它知道如何对令牌进行解码、
令牌解析方法:
使用DefaultTokenServices 在资源服务器本地配置令牌存储、解码、解析方式。
使用RemoteTokenServices 资源服务器通过HTTP请求解码令牌,每次都请求授权服务器端点/oauth/check_token
。
***HttpSecurity***配置这个雨SpringSecurity类似:
- 请求匹配器,用来设置需要进行保护的资源路径,默认的情况是保护资源服务的全部路径。
- 通过http.authorizeRequest()来设置受保护资源的访问规则。
- 其他的自定义权限保护规则通过HttpSecurity来进行配置
五、资源服务器——测试
启动授权服务和资源服务,客户端获得令牌后,可以拿着令牌去访问资源服务器,这个时候资源服务器会校验令牌的合法性。
5.1 不带令牌请求资源服务
如果没有令牌请求资源服务请求失败:
5.2 申请授权码 用户同意授权
申请授权:http://localhost:8081/oauth2/oauth/authorize?client_id=c1&client_secret=order&response_type=code&scope=all&redirect_uri=http://www.baidu.com
获得授权码:http://baidu.com/?code=nYPZQ5
5.3 携带授权码 申请令牌
5.4 校验令牌
授权服务器校验令牌端点:http://localhost:8081/oauth2/oauth/check_token
,POST
请求,响应中返回用户的身份信息及客户端id和权限范围等:
5.5 请求资源服务 校验令牌
请求资源服务需要携带token令牌,在请求头中,请求头Key为:Authorization
,请求头Value为:Bearer token值
。
注意:Bearer与token之间有空格隔开。
由于我们定义的资源方法需要有ADMIN
的角色认证,所以我们分别使用zs
和ls
申请的令牌请求资源服务:
5.5.1 以zs申请的令牌访问资源
zs
为ADMIN
角色,可以请求资源服务成功:
5.5.2 以ls申请的令牌访问资源
ls
为USER
角色,则不能访问资源成功:
401 Unauthorized 报错
后台抛异常,前端页面浏览器显示状态码500
,内部错误。是不准确的。但事实上是401
错误
因后端抛出了异常导致。
原因是认证失败了,可能资源服务器和授权服务器两个配置的信息不对应,可能有以下几个问题:
- 资源ID不对。
- 客户端ID不对。
- 客户端秘钥不对(在授权服务器配置客户端详细信息的时候客户端秘钥使用了BCryptPasswordEncoder加密,而资源服务器解析令牌的时候不需要将客户端秘钥进行BCrypt编码)。
源码
代码已上传码云
https://gitee.com/L1692312138/spring-cloud-alibaba
附录
本编文章介绍了OAuth2.0的认证服务的流程和简单实现,下篇文章将介绍使用JWT
令牌的形式来进行资源服务的认证;客户端详细信息、授权码、令牌这些信息如何存储在数据库中。
因为本文中的资源服务校验令牌是远程调用的授权服务的/oauth/check_token
令牌服务端点,而使用JWT
的形式,则不需要进行远程调用,JWT本身就包含了所有的认证信息。