Spring Security Oauth2

Spring Security Oauth2

(7条消息) Spring Security、oauth2、单点登陆SSO的关系_spring security sso_云川之下的博客-CSDN博客

SpringBoot整合spring-security-oauth2完整实现例子 - 腾讯云开发者社区-腾讯云 (tencent.com)

问:spring security鉴权服务器的实现方式有哪几种,以及接口级权限校验如何进行鉴权服务器的设计

1
2
3
4
5
6
7
8
9
10
11
12
Spring Security 是一个基于 Spring 的安全框架,提供了完备的安全解决方案,包括身份验证、授权、攻击防护等功能。在使用 Spring Security 时,我们可以采用以下几种方式实现鉴权服务器:

基于 Session 的鉴权
通过在用户登录成功后在服务器端创建一个 Session,并将 Session ID 返回给客户端浏览器,在后续操作中,客户端浏览器会将该 Session ID 发送到服务器端,从而进行用户身份的鉴别和权限的控制。

基于 Token 的鉴权
Token 鉴权是指,在客户端请求服务器时,将一段特定的字符串作为参数传递给服务器,服务器根据该 Token 进行用户身份鉴别和权限控制。Token 可以保存在 Cookie、Header 或请求参数中,具有较好的通用性和跨域支持能力。

OAuth2 鉴权
OAuth2 是目前应用最广泛的认证与授权协议之一,它通过向第三方应用颁发访问令牌(Access Token),来确保第三方应用可以获取到用户的信息或进行相关操作。

对于接口级别的权限校验,Spring Security 主要通过定义拦截器或过滤器(Filter)来实现。我们可以通过自定义 Filter 对请求进行过滤,并在其中添加相应的鉴权逻辑,在鉴权通过的情况下才允许用户继续访问受保护的资源。同时,Spring Security 还提供了丰富的注解和标签,可以在控制器(Controller)或业务逻辑层(Service)中对方法进行鉴权。例如,@PreAuthorize 和 @PostAuthorize 注解可以分别在方法执行前后进行鉴权,确保只有满足特定条件的用户才能够调用该方法。

authentication 权限问题

token作为信息校验的载体

访问一个接口,判断用户是否需要 某个角色的权限,

接口级权限校验:自定义@PreAuthorize 注解的判定方式,

问:接口级权限校验的实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1、gateway 网关统一进行token校验解析,转发请求等, 资源服务器根据网关转发的信息,进行权限校验

网关在认证授权体系里主要负责两件事:
1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
2)令牌解析并转发当前登录用户信息(明文token)给微服务

微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
1)用户授权拦截(看当前用户是否有权访问该资源)
2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)

问题:直接访问接口,会失效吗

2、资源服务器 和 授权服务器绑定 √
xx服务器实现 认证请求的转发与解析,资源服务器可以直接将解析后的数据用于判定用户是否具有访问权限
问题:服务器业务逻辑过于复杂

问:拥有后台接口的全路径uri,如何避免直接访问到接口

1
2
3
4
5
6
7
8
9
10
11
12
13
如果已经有后台接口的全路径URI,想要避免直接访问到接口,可以考虑以下措施:

1.认证验证:为后台接口添加认证方案(比如JWT令牌、OAuth2等),只有通过认证的用户才能访问相应的接口。

2.API网关:使用API网关来对所有请求进行管理,只有经过网关转发的请求才能够到达后台接口。这样可以有效地隔离外部直接访问后台的风险。

3.URL重写:在Web服务器层面上可以通过URL重写技术,将后台接口的URL地址伪装成其他不易被猜测到的形式,例如随机生成URL路径或加密URL参数。

4.IP白名单:在服务器防火墙或反向代理中设置IP白名单,只有指定的IP地址才可以访问后台接口。

5.加密传输:使用HTTPS协议进行加密传输,这样可以有效防止黑客窃取数据包,保护网络安全。

以上措施都是可以有效避免直接访问后台接口的方法,建议根据实际情况,综合考虑采取一定的组合解决方案。

API网关

API网关(API GATEWAY)是什么?有什么作用? - 知乎 (zhihu.com)

统一认证,分发请求

网关:网关进行统一权限验证

OAuth - (Open Authority)

OAuth 2.0 — OAuth

验证授权的开放标准

组件:资源拥有者 Resource Owner、第三方软件 Client:、授权服务 Authorization Server、资源服务 Resource Server

  • Resource Owner: 用户拥有资源服务器上面的数据。例如:我是一名Facebook的用户,我拥有我的Facebook 个人简介的信息。
  • Resource Server: 存储用户信息的API Service
  • Client: 想要访问用户的客户端
  • Authorization Server: OAuth的主要引擎,授权服务器,获取token。、

img

image-20210519120117924

(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源

1、OAuth 2.0 简介

OAuth2.0说明文档 | 说明 (lmlphp.com)

OAuth为应用提供了一种访问受保护资源的方法。在应用访问受保护资源之前,它必须先从资源拥有者处获取授权(访问许可),然后用访问许可交换访问令牌(代表许可的作用域、持续时间和其它属性)。应用通过向资源服务器出示访问令牌来访问受保护资源。

下图中的名词说明

Resource Owner:用户

User-Agent:浏览器

Client:应用服务器

Authorization Server:认证服务器(用户信息存放服务的认证服务器)

Resource Server:资源服务器(用户真正信息存放的服务器,需要通过access_token进行访问)

Web-Hosted Client Resource:web托管的客户端资源(这个确切的说,我也不知道叫啥才适合)

OAuth2.0服务支持以下5种获取Access Token的方式:(图片来源:OAuth2.0协议草案

1.1、APP密钥模式(Client Credentials Flow)

直接使用app密钥,不另作说明

x

1.2、Resource Owner Password Credentials Flow

直接使用用户密码,不另作说明

x (1)

1.3、Authorization Code Flow

*授权码模式*

x (2)

流程简单表述为,当用户访问应用服务器

***(A)*应用服务器返回带有授权信息的重定向链接,浏览器拿到链接,转为访问认证服务器(见下面:获取Authorization Code)

***(B)认证服务器*提供界面给用户进行确认授权,用户确认授权

(C)*用户确认授权后,认证服务器返回带有code的重定向链接,浏览器拿到链接,转为访问应用服务器*

**(D)应用服务器拿到code,访问认证服务器(见下面:*通过**Authorization Code**获取**Access Token*

(E)认证服务器验证后,返回access_token给应用服务器

授权码是应用弹出窗口,并将用户引导到授权服务器,传入标识符、请求作用域、本地状态,和一个重定向URI。授权服务器验证用户,获得授权,然后用重定向URI引导回应用,并传回授权码给应用。因为终端用户只在授权服务器上进行验证,所以终端用户的用户名和密码从来不用分享给应用。

获取****Authorization Code

参数名 必选 介绍
client_id True 创建应用时获得的App Key
response_type True 此值固定为“code”
redirect_uri True 授权后要回调的URI,即接收Authorization Code的URI, 其值可以是“oob”。 非“oob”值的redirect_uri所在域名必须与开发者注册应用时所提供的回调地址的域名相匹配
scope False 以空格分隔的权限列表,若不传递此参数,代表请求默认的basic权限。(目前只有basic权限)
state False hash,用于预防CSRF,用于保持请求和回调的状态,授权服务器在重定向到“redirect_uri”时,会在Query Parameter中原样回传该参数

/authorize?client_id=ABCDEFGHIJKLMNOP&response_type=code&redirect_uri=http://www.example.com/oauth_redirect&scope=basic&state=mytest

如果用户在此页面同意授权,则将重定向用户浏览器到指定的“redirect_uri”,并附带上表示授权服务所分配的Authorization Code的code参数(假设为“0987654321“),以及state参数(如果有),验证通过后,认证服务器将重定向用户浏览器到“http://www.example.com/oauth_redirect?state=mytest&code=0987654321”

说明:每一个Authorization Code的有效期为60秒(可根据需求调长一些,但原则上应尽可能短),并且只能使用一次,再次使用将无效。

通过Authorization Code获取****Access Token

通过上面第一步获得Authorization Code后,便可以用其向认证服务器换取一个Access Token。

参数名 必选 介绍
grant_type True 此值固定为“authorization_code”
code True 通过上面第一步所获得的Authorization Code
client_id True 创建应用时获得的App Key
client_secret True 应用的App Secret
redirect_uri True redirect_uri所在域名必须与开发者注册应用时所提供的回调地址的域名匹配

/access_token?grant_type=authorization_code&code=0987654321&client_id=ABCDEFGHIJKLMNOP&client_secret=abcdefg…&redirect_uri=http://www.example.com/oauth_redirect

若参数无误,服务器将返回一段JSON文本,包含以下参数:

参数名 必选 介绍
access_token True 获取的Access Token
token_type False 令牌类型“bearer”或“mac”,暂无很明确的说明,通常是brearer或省略
expires_in True Access Token的有效期,以秒为单位
refresh_token False 用于刷新Access Token 的 Refresh Token
scope False Access Token最终的访问范围,即用户实际授予的权限列表,用户在授权页面时,有可能会取消掉某些请求的权限,通常只作或只有登录认证的话,可忽略

示例:Content-Type: application/json

{“access_token”:”uuvvwwxxyyzz”,”expires_in”:”3600”, “scope”:”basic”,”refresh_token”:”qwertyuiop”}

1.4、Implicit Grant Flow

简化模式,该模式下,通常client和user-agent是在一起的,如手机app等

x (3)

流程简单表述为,当用户访问应用服务

***(A)*应用服务返回带有授权信息的重定向链接,浏览器拿到链接,转为访问认证服务器

***(B)认证服务器*提供界面给用户进行确认授权,用户确认授权

(C)*假设资源所有者授予访问权限,则认证服务器返回带有获取access_token的令牌的链接*

(D)浏览器链接重定向到一个web客户端资源,访问资源服务器

(E)返回一个网页(通常是一个嵌入式脚本的HTML文档)

(F)浏览器从脚本中提取access_token(包括其他参数)

(G)浏览器带着access_token重定向到应用服务器,应用服务器到浏览器传来的access_token

采用Implicit Grant方式获取Access Token的授权验证流程又被称为User-Agent Flow,适用于所有无Server端配合的应用(由于应用往往位于一个User Agent里,如浏览器里面,因此这类应用在某些平台下又被称为Client-Side Application),如手机/桌面客户端程序、浏览器插件等,以及基于JavaScript等脚本语言实现的应用。

1.5、Refreshing an Expired Access Token

令牌刷新方式,适用于所有有Server端配合的应用,其实就是更新access_token**
**

x (4)

获取Access Token,都会拿到有效期为14天的Refresh Token,和2分钟有效期的Access Token。对于这些应用,只要用户在14天内登录,应用就可以使用Refresh Token获得新的Access Token。

要使用Refresh Token获得新的Access Token,需要应用在其服务端发送请求(推荐用POST方法)到开放平台OAuth2.0授权服务的以下地址: “/access_token”,并带上以下参数:

参数名 必选 介绍
grant_type True 必须为“refresh_token”
refresh_token True 用于刷新Access Token用的Refresh Token
client_id True 创建应用时获得的App Key
client_secret True 应用的App Secret
scope False 以空格分隔的权限列表,若不传递此参数,代表请求默认的basic权限。注:通过Refresh Token刷新Access Token时所要求的scope权限范围必须小于等于上次获取Access Token时授予的权限范围

示例:

/access_token?grant_type=refresh_token&refresh_token=qwertyuiop&client_id=ABCDEFGHIJKLMNOP&client_secret=abcdefg…&scope=basic

若请求成功服务器将返回一段JSON文本,包含以下参数

示例:Content-Type: application/json

{ “access_token”:”uuvvwwxxyyzz”,”expires_in”:”120”,”scope”:”basic”,”refresh_token”:” qwertyuiop”}

2、使用access_token调用****API

获取access_token以后,就可以使用access_token调用API接口。

调用API的URL“/json”传递需要的参数,参数的详细介绍请参照以上描述

例:/json?access_token=uuvvwwxxyyzz

2、实操

OAuth 2.0是一种授权框架,允许用户授权第三方应用访问他们在另一个服务器上的资源,而不需要将他们的用户名和密码提供给第三方应用。OAuth 2.0定义了以下四种授权类型(Grant Types),它们对应不同的请求方式和请求参数:

1、授权码授权

(Authorization Code Grant):

请求方式:HTTP POST
请求参数:

  • response_type: code(必选)
  • client_id: 第三方应用ID(必选)
  • redirect_uri: 回调URL(必选)
  • scope: 授权范围(可选)
  • state: 用于防止CSRF攻击的随机字符串(可选)

2、隐式授权

(Implicit Grant):

请求方式:HTTP GET
请求参数:

  • response_type: token(必选)

  • client_id: 第三方应用ID(必选)

  • redirect_uri: 回调URL(必选)

  • scope: 授权范围(可选)

  • state: 用于防止CSRF攻击的随机字符串(可选)

3、资源拥有者密码凭证授权

(Resource Owner Password Credentials Grant):

请求方式:HTTP POST
请求参数:

  • grant_type: password(必选)
  • client_id: 第三方应用ID(必选)
  • client_secret: 第三方应用密钥(必选)
  • username: 资源拥有者用户名(必选)
  • password: 资源拥有者密码(必选)
  • scope: 授权范围(可选)

4、客户端凭证授权

(Client Credentials Grant):

请求方式:HTTP POST
请求参数:

  • grant_type: client_credentials(必选)
  • client_id: 第三方应用ID(必选)
  • client_secret: 第三方应用密钥(必选)
  • scope: 授权范围(可选)

请注意,实际请求参数可能会根据具体提供商的OAuth 2.0实现而有所不同。在实现OAuth 2.0授权时,请务必阅读提供商的文档,以确保正确实现。

网关 和 认证、授权服务器

网关可以作为资源服务器的统一验证入口,负责拦截和验证所有请求。它可以对请求进行接口级权限验证,确保只有具有合法访问权限的请求能够通过网关并访问受保护的资源。对于不合法的请求,网关可以拦截并返回相应的错误信息。

认证服务器可以作为网关获取访问令牌(token)的途径。当客户端需要访问受保护的资源时,它可以通过与认证服务器进行交互来获取访问令牌。认证服务器负责验证客户端的身份,并颁发访问令牌给客户端。

授权服务器可以作为其他第三方客户端获取访问令牌的途径。如果有其他第三方客户端需要访问受保护的资源,它们可以通过与授权服务器进行交互来获取访问令牌。授权服务器负责验证第三方客户端的身份和权限,并颁发访问令牌给第三方客户端。

通过将网关作为资源服务器进行接口级权限验证,认证服务器作为网关获取访问令牌的途径,以及授权服务器作为其他第三方客户端获取访问令牌的途径,可以实现统一的身份验证和授权机制,提高系统的安全性和可扩展性。

对比认证服务器 和 授权服务器

认证服务器的职责是验证用户的身份并生成身份验证凭证。它的主要任务是验证用户提供的身份凭证(如用户名和密码、第三方身份提供商的令牌等),并生成相应的身份验证凭证(如令牌)。认证服务器通常与用户存储(如数据库、LDAP等)进行交互,以验证用户的身份。

举例来说,当用户在登录页面输入用户名和密码时,认证服务器会验证这些凭证是否正确,并生成一个身份验证凭证(如令牌)。这个令牌可以用于后续的请求,以证明用户的身份。

授权服务器的职责是验证客户端的身份和权限,并生成访问令牌。它的主要任务是验证客户端的身份和权限,并根据客户端的请求授权访问受保护的资源。授权服务器通常与资源服务器(受保护的API或应用程序)进行交互,以验证客户端的权限,并生成相应的访问令牌。

举例来说,当客户端需要访问受保护的资源时,它会向授权服务器发送请求,并提供客户端的身份凭证(如客户端ID和客户端密钥)。授权服务器会验证客户端的身份和权限,并生成一个访问令牌。这个访问令牌可以用于后续的请求,以证明客户端有权访问受保护的资源。

对比两个服务:

  • 认证服务器主要关注用户的身份验证,验证用户的身份凭证并生成身份验证凭证(如令牌)。
  • 授权服务器主要关注客户端的身份和权限验证,验证客户端的身份和权限,并生成访问令牌。

两个服务器在功能上有所不同,但在OAuth 2.0协议中,它们通常协同工作,以实现安全的身份验证和授权机制。认证服务器验证用户的身份,授权服务器验证客户端的身份和权限,并生成访问令牌,以确保只有经过身份验证和授权的用户和客户端才能访问受保护的资源。

如果你想了解更多关于认证服务器和授权服务器的详细信息,可以使用以下查询词进行搜索:OAuth 2.0 认证服务器和授权服务器。

网关 和 认证服务器

对于第一次访问的用户请求(为鉴权),转到认证服务器,获取分发的token,拿着token进行解析,验证权限。

自定义Grant_type == 授权

Authorization

用途:

  • 作为第三方客户端(Client Id、 Client Secret)获取token的入口
  • /oauth/authorization
  • /oauth/token

已经有了的grant_type:

  • authorization code == 授权码模式
    • === /oauth/authorize?response_type=code&redirect_uri=xxx
    • /oauth2.0/token?grant_type=authorization_code
  • refresh token (GET)
  • /oauth2.0/token?grant_type=refresh_token
  • password (POST)

2、重写 ClientDetail

在Spring Security中,可以通过自定义实现ClientDetailsService接口来整合自己分发的客户端ID和密钥。下面是一个示例代码实现:

首先,创建一个实现ClientDetailsService接口的自定义类,例如CustomClientDetailsService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.ClientRegistrationService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;

@Service
public class CustomClientDetailsService implements ClientDetailsService, ClientRegistrationService {

@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
// 根据clientId从自己的存储机制中获取客户端信息
// 这里可以根据需要从数据库、配置文件或其他存储方式中获取客户端信息
// 假设客户端信息存储在一个Map中,key为clientId,value为ClientDetails对象
Map<String, ClientDetails> clientDetailsMap = new HashMap<>();
// 从自定义存储机制中获取客户端信息
ClientDetails clientDetails = clientDetailsMap.get(clientId);
if (clientDetails == null) {
throw new ClientRegistrationException("Client not found: " + clientId);
}
return clientDetails;
}

@Override
public void addClientDetails(ClientDetails clientDetails) throws ClientAlreadyExistsException {
// 将自定义的客户端信息保存到存储机制中
// 这里可以将客户端信息保存到数据库、配置文件或其他存储方式中
// 假设客户端信息存储在一个Map中,key为clientId,value为ClientDetails对象
Map<String, ClientDetails> clientDetailsMap = new HashMap<>();
if (clientDetailsMap.containsKey(clientDetails.getClientId())) {
throw new ClientAlreadyExistsException("Client already exists: " + clientDetails.getClientId());
}
clientDetailsMap.put(clientDetails.getClientId(), clientDetails);
}

@Override
public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException {
// 更新自定义的客户端信息到存储机制中
// 这里可以更新数据库、配置文件或其他存储方式中的客户端信息
// 假设客户端信息存储在一个Map中,key为clientId,value为ClientDetails对象
Map<String, ClientDetails> clientDetailsMap = new HashMap<>();
if (!clientDetailsMap.containsKey(clientDetails.getClientId())) {
throw new NoSuchClientException("Client not found: " + clientDetails.getClientId());
}
clientDetailsMap.put(clientDetails.getClientId(), clientDetails);
}

@Override
public void updateClientSecret(String clientId, String secret) throws NoSuchClientException {
// 更新自定义的客户端密钥到存储机制中
// 这里可以更新数据库、配置文件或其他存储方式中的客户端密钥
// 假设客户端信息存储在一个Map中,key为clientId,value为ClientDetails对象
Map<String, ClientDetails> clientDetailsMap = new HashMap<>();
if (!clientDetailsMap.containsKey(clientId)) {
throw new NoSuchClientException("Client not found: " + clientId);
}
ClientDetails clientDetails = clientDetailsMap.get(clientId);
if (clientDetails instanceof BaseClientDetails) {
((BaseClientDetails) clientDetails).setClientSecret(secret);
}
}

@Override
public void removeClientDetails(String clientId) throws NoSuchClientException {
// 从存储机制中删除自定义的客户端信息
// 这里可以从数据库、配置文件或其他存储方式中删除客户端信息
// 假设客户端信息存储在一个Map中,key为clientId,value为ClientDetails对象
Map<String, ClientDetails> clientDetailsMap = new HashMap<>();
if (!clientDetailsMap.containsKey(clientId)) {
throw new NoSuchClientException("Client not found: " + clientId);
}
ClientDetails clientDetails = clientDetailsMap.remove(clientId);
if (clientDetails == null) {
throw new NoSuchClientException("Client not found: " + clientId);
}
}
}

然后,在授权服务器的配置类中,将自定义的ClientDetailsService注入到AuthorizationServerConfigurerAdapter中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

@Autowired
private CustomClientDetailsService customClientDetailsService;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(customClientDetailsService);
}

// 其他配置...

}

在上述示例中,CustomClientDetailsService实现了ClientDetailsService接口,并重写了相关方法来获取、添加、更新和删除自定义的客户端信息。然后,在授权服务器的配置类中,将CustomClientDetailsService注入到ClientDetailsServiceConfigurer中,以便在授权服务器中使用自定义的客户端信息。

请注意,上述示例代码仅为演示目的,实际的实现可能需要根据具体需求进行调整和扩展。同时,还需要根据实际情况配置其他授权服务器相关的参数和功能。

希望这个示例能够帮助你整合自己分发的客户端ID和密钥到Spring Security的授权服务器中。如果你有进一步的问题,请随时提问。

3、SDK

  • Client
  • Model

参考QQ

QQ是中国腾讯公司推出的一款即时通讯软件,它提供了第三方网站或应用通过OAuth 2.0进行授权登陆的功能。以下是QQ OAuth2.0授权的基本流程,包括重定向等过程:

  1. 第三方网站或应用与QQ互联平台注册应用,获取App ID和App Key。

  2. 用户在第三方网站或应用上点击QQ登录按钮,发起授权登录请求。第三方网站或应用生成授权链接,格式如下:

1
2
3
4
5
6
***/oauth2.0/authorize?
response_type=code&
client_id=YOUR_APP_ID&
redirect_uri=YOUR_REDIRECT_URI&
state=STATE&
scope=SCOPE

其中:

  • response_type:固定为code
  • client_id:第三方网站或应用的App ID。
  • redirect_uri:第三方网站或应用的回调URL,用于接收授权码。
  • state:用于防止CSRF攻击的随机字符串,可选。
  • scope:授权范围,可选。
  1. 用户点击授权链接,浏览器跳转到QQ登录页面。用户输入QQ账号和密码,进行登录。

  2. 用户登录成功后,QQ服务器将浏览器重定向到第三方网站或应用在步骤2中提供的redirect_uri,并附带一个授权码(code),例如:

1
***/?code=AUTHORIZATION_CODE&state=STATE
  1. 第三方网站或应用收到授权码后,将授权码发送到QQ互联平台的Token Endpoint,换取访问令牌(access token)和刷新令牌(refresh token),请求的URL和参数如下:
1
2
3
4
5
6
***/oauth2.0/token?
grant_type=authorization_code&
client_id=YOUR_APP_ID&
client_secret=YOUR_APP_KEY&
code=AUTHORIZATION_CODE&
redirect_uri=YOUR_REDIRECT_URI

其中:

  • grant_type:固定为authorization_code
  • client_id:第三方网站或应用的App ID。
  • client_secret:第三方网站或应用的App Key。
  • code:授权码。
  • redirect_uri:第三方网站或应用的回调URL,需要与步骤2中的redirect_uri一致。
  1. QQ服务器返回访问令牌和刷新令牌,例如:
1
2
3
4
5
{
"access_token": "ACCESS_TOKEN",
"expires_in": 7776000,
"refresh_token": "REFRESH_TOKEN"
}
  1. 第三方网站或应用使用访问令牌调用QQ互联平台API,获取用户信息等操作。

  2. 当访问令牌过期时,第三方网站或应用可以使用刷新令牌获取新的访问令牌,请求的URL和参数如下:

1
2
3
4
5
***/oauth2.0/token?
grant_type=refresh_token&
client_id=YOUR_APP_ID&
client_secret=YOUR_APP_KEY&
refresh_token=REFRESH_TOKEN

其中:

  • grant_type:固定为refresh_token
  • client_id:第三方网站或应用的App ID。
  • client_secret:第三方网站或应用的App Key。
  • refresh_token:刷新令牌。
  1. QQ服务器返回新的访问令牌和刷新令牌,例如:
1
2
3
4
5
{
"access_token": "NEW_ACCESS_TOKEN",
"expires_in": 7776000,
"refresh_token": "REFRESH_TOKEN"
}

至此,QQ第三方登录的授权流程完成。请注意,实际实现时请务必遵循QQ互联平台的文档和最佳实践。

Token

资源服务器如何校验access token:

OAuth 2.0没有规定资源服务器如何校验access token,只是说资源服务器与授权服务器之间自己协调。实操中一般采用以下方法校验:

对于小型Web应用:资源服务器通常与授权服务器同为一体,自然能够通过读库来校验。
对于大型Web应用:授权服务器和资源服务器通常是独立部署的,有三种方式校验:
1、使用读库令牌作为access token,在数据库存储层面做共享,使得资源服务器能够通过读库来校验。
2、使用读库令牌作为access token,由授权服务器提供一个令牌校验接口,资源服务器请求该接口来校验。OAuth2.0在补充规范RFC 7662 中定义了令牌校验接口(Introspection Endpoint)的相关标准。
3、使用自包含令牌作为access token,资源服务器和授权服务器双方约定好自包含令牌的结构、签名密钥、加密方法,资源服务器按照约定规则自行校验。

JWT

jwt三个组成部分_jwt加密算法 - 腾讯云开发者社区-腾讯云 (tencent.com)

详细介绍

理解JWT的使用场景和优劣-阿里云开发者社区 (aliyun.com)

rsa 加密rsa 签名 是两个概念!(吓得我都换行了)

这两个用法很好理解:

  • 既然是加密,自然是不希望别人知道我的消息,只有我自己才能解密,所以公钥负责加密,私钥负责解密。这是大多数的使用场景,使用 rsa 来加密。
  • 既然是签名,自然是希望别人不能冒充我发消息,只有我才能发布签名,所以私钥负责签名,公钥负责验证

所以,在客户端使用 rsa 算法生成 jwt 串时,是使用私钥来“加密”的,而公钥是公开的,谁都可以解密,内容也无法变更(篡改者无法得知私钥)。

所以,在 jwt 中并没有纯粹的加密过程,而是使加密之虚,行签名之实。

JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

1
2
3
4
{
'typ': 'JWT',
'alg': 'HS256'
}

复制

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

复制

payload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

复制

然后将其进行base64加密,得到JWT的第二部分。

1
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

复制

signature

JWT的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

1
2
3
4
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

secret – 私钥

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

JWT和cookie/session的区别及优缺点

存储位置

三者都是应用在web中对http无状态协议的补充,达到状态保持的目的

cookie:cookie中的信息是以键值对的形式储存在浏览器中,而且在浏览器中可以直接看到数据。

session:session存储在服务器中,然后发送一个cookie存储在浏览器中,cookie中存储的是session_id,之后每次请求服务器通过session_id可以获取对应的session信息

JWT:JWT存储在浏览器的storage或者cookie中。由服务器产生加密的json数据包括:header,payload和signature三部分组成。header中通常来说由token的生成算法和类型组成;payload中则用来保存相关的状态信息;signature部分由header,payload,secret_key三部分加密生成。 注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。

优缺点

cookie:

  • 优点:
  1. 结构简单。cookie是一种基于文本的轻量结构,包含简单的键值对。
  2. 数据持久。虽然客户端计算机上cookie的持续时间取决于客户端上的cookie过期处理和用户干预,cookie通常是客户端上持续时间最长的数据保留形式。
  • 缺点:
  1. 大小受到限制。大多数浏览器对 cookie 的大小有 4096 字节的限制,尽管在当今新的浏览器和客户端设备版本中,支持 8192 字节的 cookie 大小已愈发常见。
  2. 非常不安全。cookie将数据裸露在浏览器中,这样大大增大了数据被盗取的风险,所有我们不应该将中要的数据放在cookie中,或者将数据加密处理。
  3. 容易被csrf攻击。可以设置csrf_token来避免攻击。

session:

  • 优点:
  1. session的信息存储在服务端,相比于cookie就在一定程度上加大了数据的安全性;相比于jwt方便进行管理,也就是说当用户登录和主动注销,只需要添加删除对应的session就可以,这样管理起来很方便。
  • 缺点:
  1. session存储在服务端,这就增大了服务器的开销,当用户多的情况下,服务器性能会大大降低。
  2. 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
  3. 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,会限制负载均衡和集群水平拓展的能力。

JWT:

  • 优点:
  1. 因为json的通用性,jwt可以支持跨语言请求,像JAVA,JavaScript,PHP等很多语言都可以使用。
  2. 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
  3. 便于传输,JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。
  4. 不需要在服务端保存会话信息, 利于服务器横向拓展。
  • 缺点:
  1. 登录状态信息续签问题。比如设置token的有效期为一个小时,那么一个小时后,如果用户仍然在这个web应用上,这个时候当然不能指望用户再登录一次。目前可用的解决办法是在每次用户发出请求都返回一个新的token,前端再用这个新的token来替代旧的,这样每一次请求都会刷新token的有效期。但是这样,需要频繁的生成token。另外一种方案是判断还有多久这个token会过期,在token快要过期时,返回一个新的token。

  2. 用户主动注销。JWT并不支持用户主动退出登录,客户端在别处使用token仍然可以正常访问。为了支持注销,我的解决方案是在注销时将该token加入到服务器的redis黑名单中。

  3. JWT与OAuth的区别

  4. > 这两个概念总有人用混淆,所以一起介绍了。

    OAuth2是一种授权框架,用在使用第三方账号登录的情况(比如使用weibo, qq, github登录某个app)
    JWT是一种认证协议,用在前后端分离,需要简单的对后台API进行保护时使用。 > 无论使用哪种方式切记用HTTPS来保证数据的安全性;

分析UsernameByPassword - 认证

Authentication

  • 执行顺序由上到下
  • 认证服务器

Filter

过滤器链

AbstractAuthenticationProcessingFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 判断request 是否需要
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
// ....
// 调用子类实现,进行尝试鉴权
authResult = this.attemptAuthentication(request, response);
// .....
}
this.successfulAuthentication(request, response, chain, authResult);
}

// 抽象方法,留给子类实现,实现鉴权逻辑
public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;

UsernamePasswordAuthenticationFilter

子类实现鉴权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}

if (password == null) {
password = "";
}

username = username.trim();
// 用于封装 UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 调用 ProviderManager ## authenticate()
// 找到支持 当前 UsernamePasswordAuthenticationToken (Authentication) 鉴权
// 的 Provider
return this.getAuthenticationManager().authenticate(authRequest);
}
}

ProviderManager

管理鉴权 具体实现的 Provider

AuthenticationManager

父类抽象接口

1
2
3
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}

ProviderManager

子类实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// AuthenticationProvider
private List<AuthenticationProvider> providers;


public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// UsernamePasswordAuthenticationToken
Class<? extends Authentication> toTest = authentication.getClass();
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
// 调用 Provider 的support 方法,判断是否支持当前 Authentication
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}

try {
// 进行鉴权
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (AuthenticationException var14) {
lastException = var14;
}
}
}
// 返回鉴权结果
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}

if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
}

Provider

鉴权实现

AuthenticationProvider

1
2
3
4
5
6
public interface AuthenticationProvider {
实际鉴权
Authentication authenticate(Authentication var1) throws AuthenticationException;
// 判断是否是 自己支持的 Autentication (Token)
boolean supports(Class<?> var1);
}

AbstractUserDetailsAuthenticationProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;

try {
// DaoAuthenticationProvider 实现
// 从 数据库获取封装完整的 UserDetails
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
}
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}

cacheWasUsed = false;
// DaoAuthenticationProvider 实现
// 从 数据库获取封装完整的 UserDetails
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);

// 检查
this.preAuthenticationChecks.check(user);
// 检查 判断 用户密码是否相等
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}

// 检查
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}

Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
//
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

———————————下面的 都是UsernameByPassword 自己的实现 ———————————–

DaoAuthenticationProvider == 独有

对 UserDetailsAuthenticationProvider 的逻辑补充

  • 调用 Service (UsernameByPassword 独有的 实现)
1
2
3
4
5
6
7
8
9
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

// retrieveUser
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// 调用service ## loadUserByUsername
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
return loadedUser;
}
}

Service == 独有

用于封装:UserDetails

1
2
3
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

Authentication == 共有

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

Object getCredentials();

Object getDetails();

Object getPrincipal();

boolean isAuthenticated();

void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

AbstractAuthenticationToken == 共有

1
2
3
4
5
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
private final Collection<GrantedAuthority> authorities;
//
private Object details;
private boolean authenticated = false;

UsernamePasswordAuthenticationToken == 独有

和 UserDetail 一起 用于封装 username 和 password

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 530L;
private final Object principal; // principal === username
private Object credentials; // credentials == password

// Filter 封装 Token 逻辑
// UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

// 所以:
// principal === username
// credentials == password
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
}

UserDetails == 独有

获取 username 和 password 进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}

Spring Security - 认证filter

适用于单体服务

案例:Spring Security的用户名密码登录的校验流程

Spring Security 过滤器执行流程

问:spring security如何保证url被对应的过滤器拦截,并正确代理,即attemptAuthentication()方法的调用到正确的filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Spring Security使用一组过滤器链来保护Web应用程序中的URL资源。每个过滤器都有一个明确定义的目的,用于执行不同的安全任务。

在Spring Security的过滤器链中,第一条FilterChainProxy会根据请求的路径匹配到对应的过滤器链,然后依次执行过滤器链中的每个过滤器。默认情况下,Spring Security会按照以下顺序将过滤器添加到过滤器链中:

1. ChannelProcessingFilter:处理HTTP和HTTPS的问题
2. SecurityContextPersistenceFilter:处理SecurityContext的存储
3. ConcurrentSessionFilter:防止并发登录
4. LogoutFilter:处理注销操作
5. UsernamePasswordAuthenticationFilter:处理基于表单的身份验证
6. DefaultLoginPageGeneratingFilter:生成默认的登录页面
7. CasAuthenticationFilter:CAS身份验证过滤器
8. BasicAuthenticationFilter:处理basic认证
9. RequestCacheAwareFilter:处理请求的缓存
10. ExceptionTranslationFilter:转换异常
11. FilterSecurityInterceptor:基于配置的访问控制

当请求到达过滤器链时,FilterChainProxy首先会检查请求路径是否匹配某个拦截规则,如果匹配,则会将该请求交给对应的过滤器链处理。 在过滤器链内部,URL被匹配到时,存在一个用于处理身份验证的拦截器,即UsernamePasswordAuthenticationFilter。这个过滤器会拦截到用户登录请求,并通过调用attemptAuthentication()方法对用户进行身份验证。如果验证成功,Spring Security 会创建一个认证对象(Authentication),并将其保存在当前线程的SecurityContextHolder中。

因此,使用Spring Security保护URL资源需要按照以下步骤操作:

1. 定义一组过滤器链
2. 配置url匹配规则来决定哪些资源需要被保护
3. 在过滤器链中添加处理身份验证的拦截器
4. 实现自定义的身份验证逻辑,在attemptAuthentication()方法中完成身份验证
5. 如果身份验证成功,通过令牌(Token)生成认证对象,并将其保存在当前线程的SecurityContextHolder中
1
2
3
4
5
6
7
public interface Filter {
void init(FilterConfig var1) throws ServletException;

void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

void destroy();
}

配置注入

Spring Security即将弃用配置类WebSecurityConfigurerAdapter - 腾讯云开发者社区-腾讯云 (tencent.com)

HttpSecurity 和 WebSecurity 区别

在Spring Security OAuth2中,有两种配置方式:Web Security和Http Security。它们各自的作用如下:

  1. Web Security:
    Web Security用于配置认证和授权规则,以及定义用户身份验证的方式。它主要用于保护应用程序的用户界面部分,即Web页面和URL路径。通过配置Web Security,你可以定义哪些URL需要进行身份验证,以及哪些用户角色可以访问特定的URL。

Web Security的配置通常包括以下内容:

  • 定义用户身份验证的方式,例如基于表单登录、基于HTTP基本认证、基于OAuth2等。
  • 配置用户登录页面和登录成功后的跳转页面。
  • 配置用户注销的处理方式。
  • 定义访问控制规则,包括允许或拒绝特定角色的用户访问特定的URL路径。
  1. Http Security:
    Http Security用于配置OAuth2的认证和授权规则,以及定义受保护资源的访问规则。它主要用于保护应用程序的API部分,即对外提供的接口和资源。

Http Security的配置通常包括以下内容:

  • 定义OAuth2认证服务器的地址和配置信息。
  • 配置受保护资源的访问规则,包括哪些URL路径需要进行OAuth2认证和授权。
  • 配置访问受保护资源所需的权限和角色。

通过配置Http Security,你可以确保只有经过OAuth2认证和授权的客户端才能访问受保护的API资源。Http Security还提供了一些便捷的方法,用于处理OAuth2的认证和授权流程,例如自动处理OAuth2的授权码流程、简化模式等。

综上所述,Web Security用于保护应用程序的用户界面部分,定义用户身份验证和访问控制规则;Http Security用于保护应用程序的API部分,定义OAuth2的认证和授权规则。两者相互配合,可以实现全面的认证和授权机制。

好的,下面是一个示例,演示了如何使用Spring Security配置Web Security和Http Security:

  1. 配置Web Security:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll() // 允许所有用户访问公共资源
.antMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色才能访问/admin路径下的资源
.anyRequest().authenticated() // 其他路径需要身份验证
.and()
.formLogin() // 使用表单登录
.loginPage("/login") // 指定登录页面的URL
.defaultSuccessUrl("/home") // 登录成功后跳转的URL
.permitAll() // 允许所有用户访问登录页面
.and()
.logout()
.logoutUrl("/logout") // 指定注销URL
.logoutSuccessUrl("/login?logout") // 注销成功后跳转的URL
.permitAll(); // 允许所有用户注销
}
}

上述配置中,我们使用authorizeRequests()方法定义了访问控制规则。antMatchers()方法用于指定URL路径的匹配规则,permitAll()表示允许所有用户访问,hasRole()表示需要特定角色才能访问。anyRequest().authenticated()表示其他路径需要身份验证。

formLogin()方法配置了使用表单登录,loginPage()指定了登录页面的URL,defaultSuccessUrl()指定了登录成功后的跳转URL。

logout()方法配置了注销功能,logoutUrl()指定了注销URL,logoutSuccessUrl()指定了注销成功后的跳转URL。

  1. 配置Http Security:
1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").authenticated() // 需要身份验证才能访问/api路径下的资源
.anyRequest().permitAll(); // 允许所有用户访问其他资源
}
}

上述配置中,我们使用authorizeRequests()方法定义了访问控制规则。antMatchers()方法用于指定URL路径的匹配规则,authenticated()表示需要身份验证才能访问。

anyRequest().permitAll()表示允许所有用户访问其他资源。

这是一个简单的示例,说明了如何使用Spring Security配置Web Security和Http Security。根据实际需求,你可以根据具体的URL路径和角色定义更复杂的访问控制规则。

WebSecurityCofnigurerAdapter 和 SecurityCofnigurerAdapter

1
2
3
4
5
HttpSecurity和WebSecurity是Spring Security框架中的两个主要配置类。HttpSecurity用于配置HTTP请求路径级别的安全性,例如在哪些请求路径上需要进行身份验证以及使用何种授权策略;而WebSecurity则用于全局的安全性配置,如设置忽略哪些请求路径、开启或禁用跨站点请求伪造保护等。

WebSecurityConfigurerAdapter和SecurityConfigurerAdapter都是Spring Security提供的配置适配器类,用于简化配置过程。其中,WebSecurityConfigurerAdapter主要用于对WebSecurity进行配置,包括设置认证方式、用户信息服务、密码加密方式等;而SecurityConfigurerAdapter主要用于配置HttpSecurity,例如添加过滤器、授权规则等。

总的来说,WebSecurity和HttpSecurity都是用于配置Spring Security的安全性策略,但它们各自负责不同的配置任务;而WebSecurityConfigurerAdapter和SecurityConfigurerAdapter则是用于简化安全性配置的适配器类,它们也分别面向不同的配置对象。

SecurityBuilder

1
2
3
public interface SecurityBuilder<O> {
O build() throws Exception;
}

image-20230411171949920

AuthenticationManagerBuilder

image-20230411165825002

创建Manager对象

构建ProviderManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder> implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
private final Log logger = LogFactory.getLog(this.getClass());
private AuthenticationManager parentAuthenticationManager;
// 持有 authenticationProviders的集合,用于构建AuthenticationManager对象
private List<AuthenticationProvider> authenticationProviders = new ArrayList();
// 持有UserDetailsService 默认的验证方式
private UserDetailsService defaultUserDetailsService;
private Boolean eraseCredentials;
// 发布 校验成功/失败 的时间发布 ----成功后可以通过监听事件获取Authenticaiton对象
private AuthenticationEventPublisher eventPublisher;

// 构建ProviderManager对象
protected ProviderManager performBuild() throws Exception {
// 如果Provider集合为空,则无法构建 Manager对象
if (!this.isConfigured()) {
return null;
} else {
ProviderManager providerManager = new ProviderManager(this.authenticationProviders, this.parentAuthenticationManager);
if (this.eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);
}

if (this.eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(this.eventPublisher);
}

providerManager = (ProviderManager)this.postProcess(providerManager);
return providerManager;
}
}
// 添加Provider
public AuthenticationManagerBuilder authenticationProvider(
AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
return this;
}
}

ProviderManagerBuilder

1
2
3
4
public interface ProviderManagerBuilder<B extends ProviderManagerBuilder<B>> extends SecurityBuilder<AuthenticationManager> {
// 通过Provider,创建ProviderManager (创建管理 Provider - 校验提供者的对象Manager)
B authenticationProvider(AuthenticationProvider var1);
}

SecurityConfigurer

1
2
3
4
5
6
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
// 初始化
void init(B var1) throws Exception;
// 配置
void configure(B var1) throws Exception;
}

image-20230411171743153

SecurityConfigurerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
// SecurityBuilder创建
private B securityBuilder;
private SecurityConfigurerAdapter.CompositeObjectPostProcessor objectPostProcessor = new SecurityConfigurerAdapter.CompositeObjectPostProcessor();

// 内部类
private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> {
private List<ObjectPostProcessor<? extends Object>> postProcessors;

public Object postProcess(Object object) {
Iterator var2 = this.postProcessors.iterator();
while(true) {
ObjectPostProcessor opp;
Class oppType;
do {
if (!var2.hasNext()) {
return object;
}
opp = (ObjectPostProcessor)var2.next();
Class<?> oppClass = opp.getClass();
oppType = GenericTypeResolver.resolveTypeArgument(oppClass, ObjectPostProcessor.class);
} while(oppType != null && !oppType.isAssignableFrom(object.getClass()));
//
object = opp.postProcess(object);
}
}
}
}

// ObejctPostProcessror
public interface ObjectPostProcessor<T> {
<O extends T> O postProcess(O var1);
}

image-20230411164517724

image-20230411165614516

WebSecurity

WebSecurityConfigurer

WebSecurityConfigurerAdapter

这里没有使用

适配器类,用于管理配置类的注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
// 上下文
private ApplicationContext context;
//
private ContentNegotiationStrategy contentNegotiationStrategy;
private ObjectPostProcessor<Object> objectPostProcessor;
// 验证的配置类
private AuthenticationConfiguration authenticationConfiguration;
// 验证管理的创建类
private AuthenticationManagerBuilder authenticationBuilder;
//
private AuthenticationManagerBuilder localConfigureAuthenticationBldr;
//
private boolean disableLocalConfigureAuthenticationBldr;
private boolean authenticationManagerInitialized;
// 验证的管理类
private AuthenticationManager authenticationManager;
private AuthenticationTrustResolver trustResolver;

private HttpSecurity http;
private boolean disableDefaults;
}

注解引入配置类

@EnableWebSecurity

引入WebSecurityConfiguration 和 SpringWebMvcImportSelector配置类

打开Spring Security配置类

1
2
3
4
5
6
7
8
9
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
1
2
3
4
5
6
7
8
9
10
11
class SpringWebMvcImportSelector implements ImportSelector {
SpringWebMvcImportSelector() {
}

// 判断能否DispatcherServlet是否已经记载(MVC的分配类)
// 不能就返回 WebMvcSecurityConfiguration的全路径
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean webmvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", this.getClass().getClassLoader());
return webmvcPresent ? new String[]{"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration"} : new String[0];
}
}

@EnableResourceServer

引入ResourceServerConfiguration配置类 – Oauth2的资源服务器

开启资源服务器配置类

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}

@EnableAuthorizationServer

引入AuthorizationServerSecurityConfiguration 和 AuthorizationServerEndpointsConfiguration配置类

​ – Oauth2的认证服务器配置

开启授权服务器配置类

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}

过滤器

错误

调用链:Filter ## doFilter() -> GenericFilterBean ## doFilter() -> AbstractAuthenticationProcessingFilter ## doFilter() ## attemptAuthentication() -> UserNamePasswordAuthenticationFilter ## attemptAuthentication()

GenericFilterBean

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {
protected final Log logger = LogFactory.getLog(this.getClass());
// 实现BeanNameAware,获取beanName
private String beanName;
// 实现EnvironmentAware接口,获取Environment
private Environment environment;
// 实现ServletContextAware接口,获取ServletContext
private ServletContext servletContext;
// 实现Filter,通过init(FilterConfig var1)获取FilterConfig
private FilterConfig filterConfig;
//
private final Set<String> requiredProperties = new HashSet(4);
}

image-20230411162420293

AbstractPreAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
protected ApplicationEventPublisher eventPublisher;
protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RememberMeServices rememberMeServices = new NullRememberMeServices();
// 用于
private RequestMatcher requiresAuthenticationRequestMatcher;
private boolean continueChainBeforeSuccessfulAuthentication = false;
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}


public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// 判断当前请求是否满足匹配规则,满足则调用 attemptAuthentication()方法
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else
Authentication authResult;
// 在过滤器链中,调用方法尝试进行校验
authResult = this.attemptAuthentication(request, response);
// 校验不通过直接返回
if (authResult == null) {
return;
}
}
// 抽象方法,子类实现
public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;

}
UserNamePasswordAuthenticationFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}

if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 调用Manager的 authenticate()方法进行校验,
// 返回校验后的完整Authentication对象 ---或者自定义AbstractAuthenticationToken -- Token
return this.getAuthenticationManager().authenticate(authRequest);
}
}
}

image-20230411163311176

FilterChainProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
// 管理所有 过滤器链
private List<SecurityFilterChain> filterChains;
private FilterChainProxy.FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
}
// 过滤器链配置
public interface SecurityFilterChain {
boolean matches(HttpServletRequest var1);

List<Filter> getFilters();
}

自动注入

权限验证调用链

过滤器执行顺序

AuthenticationManager

Manager对象通过调用 具体的校验Provider类,调用authenticate()方法,获取Authentication对象

1
2
3
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}

ProviderManager

实现类,具体逻辑实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher;
// 可以用于校验当前xx的Provider
private List<AuthenticationProvider> providers;
protected MessageSourceAccessor messages;
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication;

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
Iterator var8 = this.getProviders().iterator();

while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest)) {
try { // 调用具体的验证逻辑
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var13) {
// 异常处理
this.prepareException(var13, authentication);
throw var13;
} catch (InternalAuthenticationServiceException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
// 如果结果为空,调用父类 AuthenticationManager进行再次验证
if (result == null && this.parent != null) {
try {
result = parentResult = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var11) {
} catch (AuthenticationException var12) {
parentException = var12;
lastException = var12;
}
}

if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
// 发布验证成功事件
this.eventPublisher.publishAuthenticationSuccess(result);
}

return result;
} else {
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
}

image-20230411142414027

AuthenticationProvider

具体的校验逻辑的实现,通过support()方法,可以专门处理一种校验方式

Sms短信校验,用户名密码登录校验,

1
2
3
4
5
6
7
8
9
10
11
public interface AuthenticationProvider {
// 实现该接口,调用校验方法,将校验结果封装到Authentication对象中,用于上下文传递
Authentication authenticate(Authentication var1) throws AuthenticationException;
// 判断当前Provider实现类 是否是 处理对应Class对象的Provider
// 如果是,则调用该方法的authentication,不是继续遍历Provider集合

// Class为实现了 Authentication接口的类 -> 实现了AbstractAuthenticationToken ->
// UsernamePasswordAuthenticationToken / SmsCodeAuthenticationTokne
boolean supports(Class<?> var1);
}

AbstractUserDetailsAuthenticationProvider

用户名密码的校验 提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(this.getClass());
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

// 验证当前Authentication(UsernamePasswordAuthenticationToken)是否能验证通过
// 验证通过则返回 完整的信息,进行上下文设置
public Authentication authenticate(Authentication authentication) {
// 从缓存获取UserDetails
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
// 如果缓存为空,调用 方法检索UserDeatil对象
// 检索User对象的方法
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
}
// 封装为Authentication对象
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
protected abstract UserDetails retrieveUser(String var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException;

}

// 通过 Dao检索校验
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
//
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;

// 调用UserDetailsService## loadUserByUsername() 获取 UserDetial
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// 通过缓存获取 或者 通过JdbcDaoImpl调用方法查询数据库获取
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
return loadedUser;
}

image-20230411142606892

UserDetailsService

通过用户名进行获取用户信息的方法接口

1
2
3
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

UserDetailsManager

用户信息管理接口

1
2
3
4
5
6
7
8
9
10
11
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails var1);

void updateUser(UserDetails var1);

void deleteUser(String var1);

void changePassword(String var1, String var2);

boolean userExists(String var1);
}
JdbcUserDetailsManager + JdbcDaoImpl

查询数据库获取UserDetails

JdbcUserDetailsManager :包含各种SQL语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// JdbcUserDetailsManager 
private String usersByUsernameQuery = "select username,password,enabled from users where username = ?";
//JdbcDaoImpl
protected List<UserDetails> loadUsersByUsername(String username) {
// 调用JdbcUserDetailsManager的sql进行查询数据库
return this.getJdbcTemplate().query(this.usersByUsernameQuery, new String[]{username}, new RowMapper<UserDetails>() {
public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
String username = rs.getString(1);
String password = rs.getString(2);
boolean enabled = rs.getBoolean(3);
return new User(username, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES);
}
});
}
InMemoryUserdetailsManager

查询缓存获取UserDetails

1
2
3
4
5
6
7
8
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = (UserDetails)this.users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
} else {
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
}
}

image-20230411150420681

image-20230411141720762

UserDetails

当前校验用户的详情信息 接口封装 — 仅用于 用户名/密码 校验过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

String getPassword();

String getUsername();

boolean isAccountNonExpired();

boolean isAccountNonLocked();

boolean isCredentialsNonExpired();

boolean isEnabled();
}

User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 500L;
private static final Log logger = LogFactory.getLog(User.class);
private String password;
private final String username;
// 当前被授予的角色权限
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
// 实现凭证删除
public void eraseCredentials() {
this.password = null;
}
}
// 用于凭证的删除
public interface CredentialsContainer {
void eraseCredentials();
}

image-20230411150753639

Authentication

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();

Object getCredentials();

Object getDetails();

Object getPrincipal();

boolean isAuthenticated();

void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

AbstractAuthenticationToken

1
2
3
4
5
6
7
8
9
// AbstractAuthenticationToken   -- token令牌
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
// 权限
private final Collection<GrantedAuthority> authorities;
// 当前需要校验的信息 ----校验后:完善的信息
private Object details;
// 是否校验通过
private boolean authenticated = false;
}

image-20230411150954164

UsernamePasswordAuthenticationToken
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 500L;
// 主要的 == username ---
private final Object principal;
// 凭证 -- password ---
private Object credentials;

// UsernamePasswordAuthenticationFilter ## attemptAuthentication()
// UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
}
SmsCodeAuthenticationTokne
1
2
3
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private final Object principal;
}

image-20230411163450073

GrantedAuthority

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取当前的权限列表 -- 
public interface GrantedAuthority extends Serializable {
String getAuthority();
}

public final class SimpleGrantedAuthority implements GrantedAuthority {
private static final long serialVersionUID = 500L;
// 封装 被授予的角色权限
private final String role;

public String getAuthority() {
return this.role;
}
}

SimpleGrantedAuthority

1
2
3
4
5
6
7
8
9
public final class SimpleGrantedAuthority implements GrantedAuthority {
private static final long serialVersionUID = 500L;
// 封装 被授予的角色权限
private final String role;

public String getAuthority() {
return this.role;
}
}

image-20230411151307233

SecurityBuilder

image-20230411141429570

Spring Security OAuth2 - 授权token

Spring Security 解析(七) —— Spring Security Oauth2 源码解析 - 掘金 (juejin.cn)

综述

两大流程:

一、访问授权服务器 /oauth/**路径,进入Endpoint进行相关处理,逻辑代码再Endpoint中;例如四种访问模式,最终通过Endpoint (在这解析request的信息 — grantType和BaseRequest子类) ,进入TokenGranter – (五个实现类)通过不同的逻辑最终生成OAuth2AccessToken / / ,然后通过TokenEnhance生成增强token

二、携带 系统定义的验证信息(用户名密码,短信验证) 通过SecurityConfigurationAdapter过滤器链的请求,通过解析URL转发到对应的过滤器(过滤器将信息,封装为 Authetication – token),调用对应的验证流程 (Provider),生成最终的携带完整信息的Authentication (无法生成则说明验证不通过,抛出异常Handler),系统通过Authentication判断有无权限。

image-20230414111137460

配置类Configuration 过滤器链

spring cloud OAuth2资源服务器的过滤器链路浅析 - 简书 (jianshu.com)

在这里插入图片描述

SecurityBuilder - 重点

用于构建 WebSecurity 的Filter 或者是 HttpSecurity的 FilterChain

1
2
3
4
// WebSecurity ---Filter , HttpSecurity ---DefaultSecurityFilterChain  / SecurityFilterChain
public interface SecurityBuilder<O> {
O build() throws Exception;
}

image-20230415232218398

AbstractSecurityBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;

public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = this.doBuild();
return this.object;
}
}
// 抽象方法 ---子类实现 AbstractConfiguredSecurityBuilder ## doBuild()
protected abstract O doBuild() throws Exception;
}

AbstractConfiguredSecurityBuilder - 重点

持有 SecurityConfigurer接口的子类

统一调度 SecurityConfigurer接口的init() 和 config() 方法 SecurityConfigurerAdapter和 WebSecurityConfigurerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// B  WebSecurity ---O Filter , B  HttpSecurity ---O  DefaultSecurityFilterChain/SecurityFilterChain
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> {
// 当前的SecurityConfigurer的子类
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers;
// 自定义添加的 SecurityConfigurer子类
private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing;
private final Map<Class<? extends Object>, Object> sharedObjects;
private final boolean allowConfigurersOfSameType;
// 配置方法构建状态状态
private AbstractConfiguredSecurityBuilder.BuildState buildState;

// 子类实现-- HttpSecurity - WebSecurity -- AuthenticationManagerBuilder
protected abstract O performBuild() throws Exception;

protected final O doBuild() throws Exception {
synchronized(this.configurers) {
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
this.beforeInit();
/*Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
Iterator var2 = configurers.iterator();
SecurityConfigurer configurer;
while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
configurer.init(this);
}*/
this.init();
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
this.beforeConfigure();
this.configure();
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
// 获取 WebSecurity的filter , HttpSecurity的
O result = this.performBuild();
this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
return result;
}
}

// SecurityConfigurer的 init()方法
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
Iterator var2 = configurers.iterator();

SecurityConfigurer configurer;
while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
configurer.init(this);
}
//
var2 = this.configurersAddedInInitializing.iterator();

while(var2.hasNext()) {
configurer = (SecurityConfigurer)var2.next();
configurer.init(this);
}

}
// SecurityConfigurer的 configure()
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
Iterator var2 = configurers.iterator();

while(var2.hasNext()) {
SecurityConfigurer<O, B> configurer = (SecurityConfigurer)var2.next();
configurer.configure(this);
}

}
}

WebSecurity

初始化:被 WebSecurityConfiguration (@EnableWebConfiguration)创建

作用:用于构建FilterChainProxy ,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/*The WebSecurity is created by WebSecurityConfiguration to create the FilterChainProxy known as the Spring Security Filter Chain (springSecurityFilterChain). The springSecurityFilterChain is the Filter that the DelegatingFilterProxy delegates to.
Customizations to the WebSecurity can be made by creating a WebSecurityConfigurer or more likely by overriding WebSecurityConfigurerAdapter.
Since:
3.2
See Also:
EnableWebSecurity, WebSecurityConfiguration*/

public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
// O-- Filter -- 构建
implements SecurityBuilder<Filter>, ApplicationContextAware {
private final Log logger = LogFactory.getLog(this.getClass());
private final List<RequestMatcher> ignoredRequests = new ArrayList();
private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList();
private WebSecurity.IgnoredRequestConfigurer ignoredRequestRegistry;
private FilterSecurityInterceptor filterSecurityInterceptor;
private HttpFirewall httpFirewall;
private boolean debugEnabled;
private WebInvocationPrivilegeEvaluator privilegeEvaluator;
private DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
private SecurityExpressionHandler<FilterInvocation> expressionHandler;
private Runnable postBuildAction;
// FilterChainProxy ---管理一些列的过滤器执行链(包含多个过滤器)
protected Filter performBuild() throws Exception {
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
//
List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize);
Iterator var3 = this.ignoredRequests.iterator();

while(var3.hasNext()) {
RequestMatcher ignoredRequest = (RequestMatcher)var3.next();
// 添加 DefaultSecurityFilterChain -
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0]));
}

var3 = this.securityFilterChainBuilders.iterator();

while(var3.hasNext()) {
SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next();
securityFilterChains.add(securityFilterChainBuilder.build());
}
// 构建FilterChainProxy ----过滤器执行链,有多个过滤器链
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
filterChainProxy.setFirewall(this.httpFirewall);
}

filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (this.debugEnabled) {
result = new DebugFilter(filterChainProxy);
}
this.postBuildAction.run();
return (Filter)result;
}
}

HttpSecurity

初始化:被SecurityConfigurerAdapter (AuthorizationServerSecurityConfigurer等子类) 构建

作用:它允许为特定的http请求配置基于web的安全性。默认情况下,它将应用于所有请求,但可以使用requestMatcher(requestMatcher)或其他类似方法进行限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// O -- DefaultSecurityFilterChain -- B-- HttpSecurity
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
private final HttpSecurity.RequestMatcherConfigurer requestMatcherConfigurer;
// 过滤器链
private List<Filter> filters = new ArrayList();
// 路径匹配判断
private RequestMatcher requestMatcher;
// 重写Order排序
private FilterComparator comparator;

protected DefaultSecurityFilterChain performBuild() throws Exception {
Collections.sort(this.filters, this.comparator);
// 封装为 DefaultSecurityFilterChain
return new DefaultSecurityFilterChain(this.requestMatcher, this.filters);
}
}

// DefaultSecurityFilterChain
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private final RequestMatcher requestMatcher;
private final List<Filter> filters;

}

/*The most basic form based configuration can be seen below. The configuration will require that any URL that is requested will require a User with the role "ROLE_USER". It also defines an in memory authentication scheme with a user that has the username "user", the password "password", and the role "ROLE_USER". For additional examples, refer to the Java Doc of individual methods on HttpSecurity.
@Configuration
@EnableWebSecurity
public class FormLoginSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
}
*/

image-20230411200251565

RequestMatcher - 组件

1
2
3
public interface RequestMatcher {
boolean matches(HttpServletRequest var1);
}

AntPathRequestMatcher

用于配置过滤器路径,判断是否匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final class AntPathRequestMatcher implements RequestMatcher, RequestVariablesExtractor {
private static final Log logger = LogFactory.getLog(AntPathRequestMatcher.class);
private static final String MATCH_ALL = "/**";
// 保存匹配路径
private final AntPathRequestMatcher.Matcher matcher;
private final String pattern;
private final HttpMethod httpMethod;
private final boolean caseSensitive;
private final UrlPathHelper urlPathHelper;
// 内部接口
private interface Matcher {
boolean matches(String var1);

Map<String, String> extractUriTemplateVariables(String var1);
}
// 调用spring工具包进行判断
private static class SpringAntMatcher implements AntPathRequestMatcher.Matcher {
// 匹配
private final AntPathMatcher antMatcher;
// 路径
private final String pattern;
}
// /**前缀匹配判断
private static class SubpathMatcher implements AntPathRequestMatcher.Matcher {
private final String subpath;
private final int length;
private final boolean caseSensitive;
}
}

AndRequestMatcher

校验方式的一种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public final class AndRequestMatcher implements RequestMatcher {
private final Log logger;
// 多个RequestMatcher
private final List<RequestMatcher> requestMatchers;

public AndRequestMatcher(List<RequestMatcher> requestMatchers) {
this.requestMatchers = requestMatchers;
}

public AndRequestMatcher(RequestMatcher... requestMatchers) {
this(Arrays.asList(requestMatchers));
}

public boolean matches(HttpServletRequest request) {
Iterator var2 = this.requestMatchers.iterator();
RequestMatcher matcher;
do {
if (!var2.hasNext()) {
this.logger.debug("All requestMatchers returned true");
return true;
}
matcher = (RequestMatcher)var2.next();
this.logger.debug("Trying to match using " + matcher);
}
// 调用具体的matches()方法实现 === Ip,Regex,MvcRequest, RequestHeader
} while(matcher.matches(request));

this.logger.debug("Did not match");
return false;
}
}


// IP
public boolean matches(HttpServletRequest request) {
return this.matches(request.getRemoteAddr());
}

public boolean matches(String address) {
InetAddress remoteAddress = this.parseAddress(address);
if (!this.requiredAddress.getClass().equals(remoteAddress.getClass())) {
return false;
} else if (this.nMaskBits < 0) {
return remoteAddress.equals(this.requiredAddress);
} else {
byte[] remAddr = remoteAddress.getAddress();
byte[] reqAddr = this.requiredAddress.getAddress();
int oddBits = this.nMaskBits % 8;
int nMaskBytes = this.nMaskBits / 8 + (oddBits == 0 ? 0 : 1);
byte[] mask = new byte[nMaskBytes];
Arrays.fill(mask, 0, oddBits == 0 ? mask.length : mask.length - 1, (byte)-1);
int i;
if (oddBits != 0) {
i = (1 << oddBits) - 1;
i <<= 8 - oddBits;
mask[mask.length - 1] = (byte)i;
}

for(i = 0; i < mask.length; ++i) {
if ((remAddr[i] & mask[i]) != (reqAddr[i] & mask[i])) {
return false;
}
}

return true;
}
}

image-20230415233714075

SecurityConfigurer – 重点

1
2
3
4
5
6
// 泛型B -- WebSecurity
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B var1) throws Exception;
// B -- WebSecurity / HttpSecurity
void configure(B var1) throws Exception;
}

image-20230415231804100

SecurityConfigurerAdapter

持有 SecurityBuilder(串联SecurityBuilder 和SeucrityConfigrer),可以调用其方法build(),构建configuration

用于配置HttpSecurity和 构建 DefaultFilterChain

会被 SecurityBuilder 调用 init() 和 config()方法

1
2
3
4
5
public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> {
// 持有 securityBuilder,可以调用其方法build(),构建configuration
private B securityBuilder;
private SecurityConfigurerAdapter.CompositeObjectPostProcessor objectPostProcessor = new SecurityConfigurerAdapter.CompositeObjectPostProcessor();
}

AuthorizationServerSecurityConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 向上传递泛型 为 O ---DefaultSecurityFilterChain, B -- HttpSecurity
public final class AuthorizationServerSecurityConfigurer extends
SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
//
private AuthenticationEntryPoint authenticationEntryPoint;
// 访问异常处理
private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler();
// 配置密码的加密方式
private PasswordEncoder passwordEncoder; // for client secrets

private String realm = "oauth2/client";

private boolean allowFormAuthenticationForClients = false;

private String tokenKeyAccess = "denyAll()";

private String checkTokenAccess = "denyAll()";

private boolean sslOnly = false;
}

image-20230415235732084

image-20230415235922873

WebSecurityConfigurer

一个空接口,用于与SecurityConfigurer区别 —- 用于配置WebSecurity

WebSecurityConfigurerAdapter

[注解](#注解引入配置类 - 入口)

作用 – 用于配置WebSecurity 和 构建Filter

会被 SecurityBuilder 调用 init() 和 config()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
SecurityConfigurer<Filter, T> {
}

@Order(100)
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
private final Log logger;
private ApplicationContext context;
private ContentNegotiationStrategy contentNegotiationStrategy;
private ObjectPostProcessor<Object> objectPostProcessor;
// Authentication(授权)配置
private AuthenticationConfiguration authenticationConfiguration;
private AuthenticationManagerBuilder authenticationBuilder;
private AuthenticationManagerBuilder localConfigureAuthenticationBldr;
private boolean disableLocalConfigureAuthenticationBldr;
private boolean authenticationManagerInitialized;
private AuthenticationManager authenticationManager;
private AuthenticationTrustResolver trustResolver;
private HttpSecurity http;
private boolean disableDefaults;

// WebSecurity 继承自 父接口
public void configure(WebSecurity web) throws Exception {
}
// 子类重写配置方法
protected void configure(HttpSecurity http) throws Exception {
((HttpSecurity)((HttpSecurity)((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated().and()).formLogin().and()).httpBasic();
}
}

image-20230415225357091

image-20230416000039830

ResourceServerConfiguration

通过**@EnableResourceServer**注解引入,调度 ResourceServerConfigurer的方法 config(),添加各种配置信息,例如 资源服务器配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {

private int order = 3;

@Autowired(required = false)
private TokenStore tokenStore;
@Autowired(required = false)
private AuthenticationEventPublisher eventPublisher;

@Autowired(required = false)
private Map<String, ResourceServerTokenServices> tokenServices;

@Autowired
private ApplicationContext context;
// 调度 ResourceServerConfigurer
private List<ResourceServerConfigurer> configurers = Collections.emptyList();

@Autowired(required = false)
// @EnableAuthorizationServer ## @Import({AuthorizationServerEndpointsConfiguration.class})
private AuthorizationServerEndpointsConfiguration endpoints;
/*@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {

}*/

AuthorizationServerSecurityConfiguration

通过**@EnableAuthorizationServer**注解 引入,调度 AuthorizationServerConfigurer的方法 config(),添加各种配置信息,(下文有详情 @EnableAuthorizationServer

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@Order(0)
@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
// AuthorizationServerConfigurer
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

@Autowired
private ClientDetailsService clientDetailsService;
}

fimage-20230416102157558

Configurer

WebSecurityConfigurerAdapter的子类 — (WebSecurity)

ResourceServerConfigurationAuthorizationServerSecurityConfiguration 持有

用于配置信息

ResourceServerConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface ResourceServerConfigurer {

/**
* Add resource-server specific properties (like a resource id). The defaults should work for many applications, but
* you might want to change at least the resource id.
*
* @param resources configurer for the resource server
* @throws Exception if there is a problem
*/
void configure(ResourceServerSecurityConfigurer resources) throws Exception;

/**
* Use this to configure the access rules for secure resources. By default all resources <i>not</i> in "/oauth/**"
* are protected (but no specific rules about scopes are given, for instance). You also get an
* {@link OAuth2WebSecurityExpressionHandler} by default.
*
* @param http the current http filter configuration
* @throws Exception if there is a problem
*/
void configure(HttpSecurity http) throws Exception;

}

ResourceServerConfigurerAdapter

1
2
3
4
5
6
7
8
9
10
11
12
public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
}

@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}

}

image-20230415225845688

ResourceSecurityConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 也是用于自动注入 org.springframework.boot.autoconfigure
package org.springframework.boot.autoconfigure.security.oauth2.resource;

@Configuration
@Conditional({OAuth2ResourceServerConfiguration.ResourceServerCondition.class})
@ConditionalOnClass({EnableResourceServer.class, SecurityProperties.class})
@ConditionalOnWebApplication
@ConditionalOnBean({ResourceServerConfiguration.class})
//
@Import({ResourceServerTokenServicesConfiguration.class})
public class OAuth2ResourceServerConfiguration {
private final ResourceServerProperties resource;
protected static class ResourceSecurityConfigurer extends ResourceServerConfigurerAdapter {
private ResourceServerProperties resource;

public ResourceSecurityConfigurer(ResourceServerProperties resource) {
this.resource = resource;
}

public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(this.resource.getResourceId());
}

public void configure(HttpSecurity http) throws Exception {
((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();
}
}
}

image-20230416103930151

AuthorizationServerConfigurer

image-20230416104056853

AuthorizationServerSecurityConfigurer

ClientDetailsServiceConfigurer

AuthorizationServerEndpointsConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*an OAUth2 Authorization Server配置. 通过 @EnableAuthorizationServer.注入*/
public interface AuthorizationServerConfigurer {

/**
* /oauth/token endpoint. The
* /oauth/authorize endpoint also needs to be secure, but that is a normal user-facing endpoint and should be
* secured the same way as the rest of your UI, so is not covered here.
*/
void configure(AuthorizationServerSecurityConfigurer security) throws Exception;

/**
* Configure the {@link ClientDetailsService}, e.g. declaring individual clients and their properties. Note that
* password grant is not enabled (even if some clients are allowed it) unless an {@link AuthenticationManager} is
* supplied to the {@link #configure(AuthorizationServerEndpointsConfigurer)}. At least one client, or a fully
* formed custom {@link ClientDetailsService} must be declared or the server will not start.
*
* @param clients the client details configurer
*/
void configure(ClientDetailsServiceConfigurer clients) throws Exception;

/**
* Configure the non-security features of the Authorization Server endpoints, like token store, token
* customizations, user approvals and grant types. You shouldn't need to do anything by default, unless you need
* password grants, in which case you need to provide an {@link AuthenticationManager}.
*
* @param endpoints the endpoints configurer
*/
void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception;

}

AuthorizationServerConfigurerAdapter

适配器

1
2
3
4
5
6
7
8
9
10
11
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
}

AuthorizationServerConfigurerAdapter

OAuth2AuthorizationServerConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Spring Boot整合security.oauth2
package org.springframework.boot.autoconfigure.security.oauth2.authserver;

@Configuration
@ConditionalOnClass({EnableAuthorizationServer.class})
@ConditionalOnMissingBean({AuthorizationServerConfigurer.class})
@ConditionalOnBean({AuthorizationServerEndpointsConfiguration.class})
// 引入配置类 ---spring boot auto-configuration
@EnableConfigurationProperties({AuthorizationServerProperties.class})
// JWT xx
@Import({AuthorizationServerTokenServicesConfiguration.class})
public class OAuth2AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final Log logger = LogFactory.getLog(OAuth2AuthorizationServerConfiguration.class);
private final BaseClientDetails details;
private final AuthenticationManager authenticationManager;
private final TokenStore tokenStore;
private final AccessTokenConverter tokenConverter;
private final AuthorizationServerProperties properties;

image-20230415225945367

image-20230415225321835

注解引入配置类 - 入口

@EnableWebSecurity

引入WebSecurityConfigurationSpringWebMvcImportSelector配置类

打开Spring Security配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class})
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}

/*@EnableGlobalAuthentication*/
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
WebSecurityConfiguration

持有SecurityConfigurer –》 webSecurityConfigurers 用于构建WebSecurity

SecurityConfigurer(关键) 用于串联 – SecurityBuilder(关键)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
// 构建 WebSecurity
private WebSecurity webSecurity;

private Boolean debugEnabled;
// webSecurityConfigurers 用于构建
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

private ClassLoader beanClassLoader;

@Autowired(required = false)
private ObjectPostProcessor<Object> objectObjectPostProcessor;
SpringWebMvcImportSelector

用于获取WebMvcSecurityConfiguration,将其注入BeanFactory

1
2
3
4
5
6
7
8
9
10
11
class SpringWebMvcImportSelector implements ImportSelector {
SpringWebMvcImportSelector() {
}

// 判断能否DispatcherServlet是否已经记载(MVC的分配类)
// 不能就返回 WebMvcSecurityConfiguration的全路径
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean webmvcPresent = ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", this.getClass().getClassLoader());
return webmvcPresent ? new String[]{"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration"} : new String[0];
}
}

@EnableResourceServer

继承自WebSecurityConfigurerAdapter

引入配置类ResourceServerConfiguration – Oauth2的资源服务器

开启资源服务器配置类

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ResourceServerConfiguration.class})
public @interface EnableResourceServer {
}
ResourceServerConfiguration

资源服务器配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {
private int order = 3;
@Autowired(
required = false
)
private TokenStore tokenStore;
@Autowired(
required = false
)
private AuthenticationEventPublisher eventPublisher;
@Autowired(
required = false
)
// ResourceServerTokenServices 远程调用代理实现类 --
// 接口,用于(代理)获取Authentication 鉴权
private Map<String, ResourceServerTokenServices> tokenServices;
@Autowired
private ApplicationContext context;
// 持有 ResourceServerConfigurer ----实现类为XXXAdapter
private List<ResourceServerConfigurer> configurers = Collections.emptyList();
@Autowired(
required = false
) //
private AuthorizationServerEndpointsConfiguration endpoints;

}

image-20230411203927984

@EnableAuthorizationServer

<span id = “@EnableAuthorizationServer2”>@EnableAuthorizationServer

WebSecurityConfigurerAdapter管理这两个类

引入AuthorizationServerSecurityConfigurationAuthorizationServerEndpointsConfiguration配置类

​ – Oauth2的认证服务器配置

开启授权服务器配置类

1
2
3
4
5
6
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {
}
AuthorizationServerSecurityConfiguration

配置认证服务器配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerEndpointsConfiguration endpoints;

protected void configure(HttpSecurity http) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
FrameworkEndpointHandlerMapping handlerMapping = this.endpoints.oauth2EndpointHandlerMapping();
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
this.configure(configurer);
http.apply(configurer);
// 配置默认 不需要拦截的路径
String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
if (!this.endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
UserDetailsService userDetailsService = (UserDetailsService)http.getSharedObject(UserDetailsService.class);
this.endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
}

((RequestMatcherConfigurer)((HttpSecurity)((AuthorizedUrl)((AuthorizedUrl)((AuthorizedUrl)http.authorizeRequests().antMatchers(new String[]{tokenEndpointPath})).fullyAuthenticated().antMatchers(new String[]{tokenKeyPath})).access(configurer.getTokenKeyAccess()).antMatchers(new String[]{checkTokenPath})).access(configurer.getCheckTokenAccess()).and()).requestMatchers().antMatchers(new String[]{tokenEndpointPath, tokenKeyPath, checkTokenPath})).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.setSharedObject(ClientDetailsService.class, this.clientDetailsService);
}
}

image-20230416082643200

AuthorizationServerEndpointsConfiguration

1用于注册TokenKeyEndpointRegistrar内部类

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@Import({AuthorizationServerEndpointsConfiguration.TokenKeyEndpointRegistrar.class})
public class AuthorizationServerEndpointsConfiguration {
// 配置授权服务器端点的属性和增强的功能
private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
@Autowired //
private ClientDetailsService clientDetailsService;
@Autowired // AuthorizationServerConfigurer 用于配置 认证服务器
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
}

// TokenKeyEndpointRegistrar ----内部类,通过BeanDefinitionRegisterPostProcessor 注册JwtAccessTokenConverter 对象

image-20230411204322994

Token转化

Json,OAuth2Authentication (Authentication), OAuth2AccessToken (封装后的Token对象)

信息保存

OAuth2AccessToken

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public interface OAuth2AccessToken {

public static String BEARER_TYPE = "Bearer";

public static String OAUTH2_TYPE = "OAuth2";
// 授权服务器颁发的访问令牌。这个值是REQUIRED。
// 就是token
public static String ACCESS_TOKEN = "access_token";
//
public static String TOKEN_TYPE = "token_type";

public static String EXPIRES_IN = "expires_in";

public static String REFRESH_TOKEN = "refresh_token";
public static String SCOPE = "scope";

// 转
Map<String, Object> getAdditionalInformation();
OAuth2RefreshToken getRefreshToken();
}

public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {

private static final long serialVersionUID = 914967629530462926L;

private String value;

private Date expiration;

private String tokenType = BEARER_TYPE.toLowerCase();

private OAuth2RefreshToken refreshToken;

private Set<String> scope;
// 额外信息Map
private Map<String, Object> additionalInformation = Collections.emptyMap();
}

OAuth2Authentication

1
2
3
4
5
6
7
8
// An OAuth 2 authentication token can contain two authentications: one for the client and one for the user
public class OAuth2Authentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -4809832298438307309L;
// OAuth2Request请求信息封装
private final OAuth2Request storedRequest;
// 验证前后信息
private final Authentication userAuthentication;
}

image-20230416144137597

TokenStore

持久化token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
/**
* Persistence interface for OAuth2 tokens.
*/
public interface TokenStore {

/**
* Read the authentication stored under the specified token value.
*
* @param token The token value under which the authentication is stored.
* @return The authentication, or null if none.
*/
OAuth2Authentication readAuthentication(OAuth2AccessToken token);

/**
* Read the authentication stored under the specified token value.
*
* @param token The token value under which the authentication is stored.
* @return The authentication, or null if none.
*/
OAuth2Authentication readAuthentication(String token);

/**
* Store an access token.
*
* @param token The token to store.
* @param authentication The authentication associated with the token.
*/
void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);

/**
* Read an access token from the store.
*
* @param tokenValue The token value.
* @return The access token to read.
*/
OAuth2AccessToken readAccessToken(String tokenValue);

/**
* Remove an access token from the store.
*
* @param token The token to remove from the store.
*/
void removeAccessToken(OAuth2AccessToken token);

/**
* Store the specified refresh token in the store.
*
* @param refreshToken The refresh token to store.
* @param authentication The authentication associated with the refresh token.
*/
void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication);

/**
* Read a refresh token from the store.
*
* @param tokenValue The value of the token to read.
* @return The token.
*/
OAuth2RefreshToken readRefreshToken(String tokenValue);

/**
* @param token a refresh token
* @return the authentication originally used to grant the refresh token
*/
OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token);

/**
* Remove a refresh token from the store.
*
* @param token The token to remove from the store.
*/
void removeRefreshToken(OAuth2RefreshToken token);

/**
* Remove an access token using a refresh token. This functionality is necessary so refresh tokens can't be used to
* create an unlimited number of access tokens.
*
* @param refreshToken The refresh token.
*/
void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken);

/**
* Retrieve an access token stored against the provided authentication key, if it exists.
*
* @param authentication the authentication key for the access token
*
* @return the access token or null if there was none
*/
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

/**
* @param clientId the client id to search
* @param userName the user name to search
* @return a collection of access tokens
*/
Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName);

/**
* @param clientId the client id to search
* @return a collection of access tokens
*/
Collection<OAuth2AccessToken> findTokensByClientId(String clientId);

}

image-20230416233446586

TokenEnhancer

生成token时用于增强token

1
2
3
4
5
6
7
8
9
10
public interface TokenEnhancer {
/**
* Provides an opportunity for customization of an access token (e.g. through its additional information map) during
* the process of creating a new token for use by a client.
// 返回一携带额外信息的 OAuth2AccessToken
* @return a new token enhanced with additional information
*/
OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication);

}

AccessTokenConverter

在OAuth2AccessToken 、 OAuth2Authorization 和 Json()之间的想换转化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface AccessTokenConverter {

final String AUD = "aud";

final String CLIENT_ID = "client_id";

final String EXP = "exp";

final String JTI = "jti";

final String GRANT_TYPE = "grant_type";

final String ATI = "ati";

final String SCOPE = OAuth2AccessToken.SCOPE;

final String AUTHORITIES = "authorities";
// 将 OAuth2AccessToken, OAuth2Authentication转化为 Json信息(Map)
// token未加密前
Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
// 提取OAuth2AccessToken
OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);
// 提取Authentication信息
OAuth2Authentication extractAuthentication(Map<String, ?> map);

}

JwtAccessTokenConverter

继承两个接口TokenEnhancer AccessTokenConverter,实习那两个接口的所有功能

通过设置secrete (jwt 第三部分进行加密,将OAuth2AccessToken 转为 JWT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Helper that translates between JWT encoded token values and OAuth authentication information (in both directions).  Also acts as a TokenEnhancer when tokens are granted.
// 继承两个接口,实习那两个接口的所有功能
public class JwtAccessTokenConverter implements TokenEnhancer, AccessTokenConverter, InitializingBean {

/**
* Field name for token id.
*/
public static final String TOKEN_ID = AccessTokenConverter.JTI;

/**
* Field name for access token id.
*/
public static final String ACCESS_TOKEN_ID = AccessTokenConverter.ATI;

private static final Log logger = LogFactory.getLog(JwtAccessTokenConverter.class);

private AccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();

private JwtClaimsSetVerifier jwtClaimsSetVerifier = new NoOpJwtClaimsSetVerifier();

private JsonParser objectMapper = JsonParserFactory.create();

private String verifierKey = new RandomValueStringGenerator().generate();

private Signer signer = new MacSigner(verifierKey);

private String signingKey = verifierKey;

private SignatureVerifier verifier;
}

image-20230416084046723

实现OAuth2协议的API接口,可以用于微服务之间的权限验证

Spring Security 部分

1
2
3
4
5
6
7
既可以应用到鉴权服务器 又可以 应用 到 资源服务器
全局配置注解服务器
@EnableWebSecurity
资源服务器
@EnableResourceServer
认证服务器
@EnableAuthorizationServer

管理过滤器

FilterChainProxy

管理多个过滤器链集合 — webSecurity – Filter

1
2
3
4
5
6
7
8
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
// 多个过滤器链的集合
private List<SecurityFilterChain> filterChains;
private FilterChainProxy.FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
}

拥有一系列配置的过滤器(单个) – HttpSecurity — DefaultSecurityFilterChain

1
2
3
4
5
6
7
8
9
10
public interface SecurityFilterChain {
boolean matches(HttpServletRequest var1);
List<Filter> getFilters();
}

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
}

被管理过滤器

Spring Security结合JWT实现认证与授权 - 掘金 (juejin.cn)

过滤器执行顺序

1
public class OAuth2ClientContextFilter implements Filter, InitializingBean {}

OAuth2AuthenticationProcessingFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean {
private static final Log logger = LogFactory.getLog(OAuth2AuthenticationProcessingFilter.class);
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
//
private AuthenticationManager authenticationManager;
// 详细信息
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
// token提取器
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
private AuthenticationEventPublisher eventPublisher = new OAuth2AuthenticationProcessingFilter.NullEventPublisher();
private boolean stateless = true;
}

// 提取请求中的token信息,封装到 PreAuthenticatedAuthenticationToken
// head: Authorization
// body: access_token
public interface TokenExtractor {
Authentication extract(HttpServletRequest var1);
}

TokenEndpointAuthenticationFilter

1
2
3
4
5
6
7
public class TokenEndpointAuthenticationFilter implements Filter {
private static final Log logger = LogFactory.getLog(TokenEndpointAuthenticationFilter.class);
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
private final AuthenticationManager authenticationManager;
private final OAuth2RequestFactory oAuth2RequestFactory;
}

OAuth2ClientContextFilter

1
2
3
4
5
public class OAuth2ClientContextFilter implements Filter, InitializingBean {
public static final String CURRENT_URI = "currentUri";
private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
}

Manager

OAuth2AuthenticationManager

1
2
3
4
5
public class OAuth2AuthenticationManager implements AuthenticationManager, InitializingBean {
private ResourceServerTokenServices tokenServices;
private ClientDetailsService clientDetailsService;
private String resourceId;
}

端点-Endpoint - API

API

  • AuthorizationEndpoint 用于服务于授权请求。预设地址:/oauth/authorize。
  • TokenEndpoint 用于服务访问令牌的请求。预设地址:/oauth/token。
1
/oauth/token -- TokenEndpoint 请求token
1
/oauth/token_key -- TokenKeyEndpoint token的关键信息(算法,验证器key)
1
/oauth/authorize -- AuthorizationEndpoint 授权token(分发code)
1
/oauth/check_token -- CheckTokenEndpoint token检查
1
/oauth/confirm_access -- WhitelabelApprovalEndpoint 确认访问
1
/oauth/error -- WhitelabelErrorEndpoint 错误请求

AbstractEndpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class AbstractEndpoint implements InitializingBean {
private WebResponseExceptionTranslator<OAuth2Exception> providerExceptionHandler = new DefaultWebResponseExceptionTranslator();
// token授予的模式 -- 四种
private TokenGranter tokenGranter;
// 用于获取ClientDetail信息
private ClientDetailsService clientDetailsService;
// Request请求封装的工厂
private OAuth2RequestFactory oAuth2RequestFactory;
private OAuth2RequestFactory defaultOAuth2RequestFactory;

// 实现InitializingBean接口,会在属性填充后进行调用
public void afterPropertiesSet() throws Exception {
Assert.state(this.tokenGranter != null, "TokenGranter must be provided");
Assert.state(this.clientDetailsService != null, "ClientDetailsService must be provided");
this.defaultOAuth2RequestFactory = new DefaultOAuth2RequestFactory(this.getClientDetailsService());
if (this.oAuth2RequestFactory == null) {
this.oAuth2RequestFactory = this.defaultOAuth2RequestFactory;
}

}
}


// 两个实现类,类似于UserDetailsService
// InMemoryClientDetailsService 在内存中寻找
// JdbcClientDetailsService 在数据库中寻找
public interface ClientDetailsService {
// 通过ClientId获取当前client客户端的信息
ClientDetails loadClientByClientId(String var1) throws ClientRegistrationException;
}



image-20230411182043644

ClientDetials

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface ClientDetails extends Serializable {
String getClientId();

Set<String> getResourceIds();

boolean isSecretRequired();

String getClientSecret();

boolean isScoped();

Set<String> getScope();

Set<String> getAuthorizedGrantTypes();

Set<String> getRegisteredRedirectUri();

Collection<GrantedAuthority> getAuthorities();

Integer getAccessTokenValiditySeconds();

Integer getRefreshTokenValiditySeconds();

boolean isAutoApprove(String var1);

Map<String, Object> getAdditionalInformation();
}
BaseClientDetails
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class BaseClientDetails implements ClientDetails {
@JsonProperty("client_id")
@com.fasterxml.jackson.annotation.JsonProperty("client_id")
private String clientId;
@JsonProperty("client_secret")
@com.fasterxml.jackson.annotation.JsonProperty("client_secret")
private String clientSecret;
@JsonDeserialize(
using = JacksonArrayOrStringDeserializer.class
)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
using = Jackson2ArrayOrStringDeserializer.class
)
private Set<String> scope;
@JsonProperty("resource_ids")
@JsonDeserialize(
using = JacksonArrayOrStringDeserializer.class
)
@com.fasterxml.jackson.annotation.JsonProperty("resource_ids")
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
using = Jackson2ArrayOrStringDeserializer.class
)
private Set<String> resourceIds;
@JsonProperty("authorized_grant_types")
@JsonDeserialize(
using = JacksonArrayOrStringDeserializer.class
)
@com.fasterxml.jackson.annotation.JsonProperty("authorized_grant_types")
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
using = Jackson2ArrayOrStringDeserializer.class
)
private Set<String> authorizedGrantTypes;
@JsonProperty("redirect_uri")
@JsonDeserialize(
using = JacksonArrayOrStringDeserializer.class
)
@com.fasterxml.jackson.annotation.JsonProperty("redirect_uri")
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
using = Jackson2ArrayOrStringDeserializer.class
)
private Set<String> registeredRedirectUris;
@JsonProperty("autoapprove")
@JsonDeserialize(
using = JacksonArrayOrStringDeserializer.class
)
@com.fasterxml.jackson.annotation.JsonProperty("autoapprove")
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(
using = Jackson2ArrayOrStringDeserializer.class
)
private Set<String> autoApproveScopes;
private List<GrantedAuthority> authorities;
@JsonProperty("access_token_validity")
@com.fasterxml.jackson.annotation.JsonProperty("access_token_validity")
private Integer accessTokenValiditySeconds;
@JsonProperty("refresh_token_validity")
@com.fasterxml.jackson.annotation.JsonProperty("refresh_token_validity")
private Integer refreshTokenValiditySeconds;
@JsonIgnore
@com.fasterxml.jackson.annotation.JsonIgnore
private Map<String, Object> additionalInformation;
}

AuthorizationEndpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
@SessionAttributes({AuthorizationEndpoint.AUTHORIZATION_REQUEST_ATTR_NAME, AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME})
public class AuthorizationEndpoint extends AbstractEndpoint {
static final String AUTHORIZATION_REQUEST_ATTR_NAME = "authorizationRequest";

static final String ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME = "org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST";

private AuthorizationCodeServices authorizationCodeServices = new InMemoryAuthorizationCodeServices();

private RedirectResolver redirectResolver = new DefaultRedirectResolver();

private UserApprovalHandler userApprovalHandler = new DefaultUserApprovalHandler();

private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

private OAuth2RequestValidator oauth2RequestValidator = new DefaultOAuth2RequestValidator();

private String userApprovalPage = "forward:/oauth/confirm_access";

private String errorPage = "forward:/oauth/error";

private Object implicitLock = new Object();

public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
this.sessionAttributeStore = sessionAttributeStore;
}

public void setErrorPage(String errorPage) {
this.errorPage = errorPage;
}

@RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
SessionStatus sessionStatus, Principal principal) {

// Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
// query off of the authorization request instead of referring back to the parameters map. The contents of the
// parameters map will be stored without change in the AuthorizationRequest object once it is created.
AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

Set<String> responseTypes = authorizationRequest.getResponseTypes();

if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
}

if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
}

try {

if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
throw new InsufficientAuthenticationException(
"User must be authenticated with Spring Security before authorization can be completed.");
}

ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

// The resolved redirect URI is either the redirect_uri from the parameters or the one from
// clientDetails. Either way we need to store it on the AuthorizationRequest.
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException(
"A redirectUri must be either supplied or preconfigured in the ClientDetails");
}
authorizationRequest.setRedirectUri(resolvedRedirect);

// We intentionally only validate the parameters requested by the client (ignoring any data that may have
// been added to the request by the manager).
oauth2RequestValidator.validateScope(authorizationRequest, client);

// Some systems may allow for approval decisions to be remembered or approved by default. Check for
// such logic here, and set the approved flag on the authorization request accordingly.
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
(Authentication) principal);
// TODO: is this call necessary?
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);

// Validation is all done, so we can check for auto approval...
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
(Authentication) principal));
}
}

// Store authorizationRequest AND an immutable Map of authorizationRequest in session
// which will be used to validate against in approveOrDeny()
// 存储 - authorizationRequest
model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);
model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));

return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

}
catch (RuntimeException e) {
sessionStatus.setComplete();
throw e;
}

}
}

我们来大致解析下这段逻辑:

  • 1、 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象
  • 2、 判断 principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因
  • 3、 通过 ClientDetailsService.loadClientByClientId() 获取到 ClientDetails 客户端信息
  • 4、 获取参数中的回调地址并且与系统配置的回调地址(步骤3获取到的client信息)对比
  • 5、 与步骤4一样 验证 scope
  • 6、 检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true))
  • 7、 由于我们设置 autoApprove(true) 则 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址
  • 8、 真实生成Code 的方法时 generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) 方法: 其内部是调用 authorizationCodeServices.createAuthorizationCode()方法生成code的

  生成授权码的整个逻辑其实是相对简单的,真正复杂的是token的生成逻辑,那么接下来我们就看看token的生成。

AuthorizationCodeServices

1
2
3
4
5
6
7
8
9
public interface AuthorizationCodeServices {
// 根据信息生成特殊 code
String createAuthorizationCode(OAuth2Authentication authentication);
/**
* Consume a authorization code. */
// 消费 code
OAuth2Authentication consumeAuthorizationCode(String code)
throws InvalidGrantException;
}

image-20230417175859959

TokenEndpoint

简单概括下来,整个生成token 的逻辑如下:

  • 1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
  • 2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置的客户端信息
  • 3、 通过客户端信息生成 TokenRequest 对象
  • 4、 将步骤3获取到的 TokenRequest 作为TokenGranter.grant() 方法参照 生成 OAuth2AccessToken 对象(即token)
  • 5、 返回 token

aa

在这里插入图片描述

CheckTokenEndpoint

用于检查token是否有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CheckTokenEndpoint {
// 主要的service
private ResourceServerTokenServices resourceServerTokenServices;

private AccessTokenConverter accessTokenConverter = new CheckTokenAccessTokenConverter();

@RequestMapping(value = "/oauth/check_token")
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
// 读取string token 转为 OAuth2AccessToken
OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
}

if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
}

OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());

return accessTokenConverter.convertAccessToken(token, authentication);
}

ResourceServerTokenServices

1
2
3
4
5
6
7
8
9
10
11
12
public interface ResourceServerTokenServices {

/**
* Load the credentials for the specified access token.
*/
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;

/**
* Retrieve the full access token details from just the value.
*/
OAuth2AccessToken readAccessToken(String accessToken);
}

image-20230417180224185

授权模式

TokenGranter

根据不同模式 生成token,即:通过认证后的 authentication 信息 (再封装为 toekn request),封装为对应模式的 token

  • authorization_code 授权码认证
  • client_credentials 客户端认证
  • password 密码认证
  • implicit 隐式授权认证
  • refresh_token 刷新密钥
1
2
3
4
public interface TokenGranter {
// 根据 封装好的 TokenRequest 获取 OAuth2AccessToken
OAuth2AccessToken grant(String var1, TokenRequest var2);
}
1
2
3
4
5
6
7
8
9
RefreshTokenGranter : 更新token模式

AuthorizationCodeTokenGranter :授权码模式

ImplicitTokenGranter : 简化模式
// 调用 UsernamePasswordAuthenticationProvider的执行流程
ResourceOwnerPasswordTokenGranter :密码模式

ClientCredentialsTokenGranter :客户端凭证模式

image-20230411173833116

执行链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
```



#### AbstractTokenGranter

```java
public abstract class AbstractTokenGranter implements TokenGranter {
// 获取OAuth2Token的三种方式 (创建,刷新,获取)
private final AuthorizationServerTokenServices tokenServices;
private final ClientDetailsService clientDetailsService;
private final OAuth2RequestFactory requestFactory;
private final String grantType;

// 被ClientCredentialsTokenGranter直接调用
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
if (!this.grantType.equals(grantType)) {
return null;
} else {
String clientId = tokenRequest.getClientId();
// 默认为客户端凭证模式进行验证 --
ClientDetails client = this.clientDetailsService.loadClientByClientId(clientId);
this.validateGrantType(grantType, client);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Getting access token for: " + clientId);
}
// 11 ---获取AccessToken
return this.getAccessToken(client, tokenRequest);
}
}

// 11 -- 被子类 : RefreshTokenGranter重写
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
// 222
return this.tokenServices.createAccessToken(this.getOAuth2Authentication(client, tokenRequest));
}
//
/// 222 -- 被子类: AuthorizationCodeTokenGratrer / ResourceOwnerPasswordTokenGranter重写
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
OAuth2Request storedOAuth2Request = this.requestFactory.createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, (Authentication)null);
}
}
````

#### RefreshTokenGranter

```java
public class RefreshTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "refresh_token";
// 重点再Service
// 调用父类的AuthorizationServerTokenServices ## refreshAccessToken()方法,获取OAuth2AccessToken
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
String refreshToken = (String)tokenRequest.getRequestParameters().get("refresh_token");
///AbstractTokenGranter ## AuthorizationServerTokenServices ## refreshAccessToken()
return this.getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}
}

AuthorizationCodeTokenGranter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "authorization_code";
// 重点Service
private final AuthorizationCodeServices authorizationCodeServices;
// 重点再Service如何生成的code
// AbstractTokenGranter调用 getOAuth2Authentication()进入
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = tokenRequest.getRequestParameters();
String authorizationCode = (String)parameters.get("code");
String redirectUri = (String)parameters.get("redirect_uri");
if (authorizationCode == null) {
throw new InvalidRequestException("An authorization code must be supplied.");
} else {
OAuth2Authentication storedAuth =
//
this.authorizationCodeServices.consumeAuthorizationCode(authorizationCode);
if (storedAuth == null) {
throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);
} else {
OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();
String redirectUriApprovalParameter = (String)pendingOAuth2Request.getRequestParameters().get("redirect_uri");
if ((redirectUri != null || redirectUriApprovalParameter != null) && !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) {
throw new RedirectMismatchException("Redirect URI mismatch.");
} else {
String pendingClientId = pendingOAuth2Request.getClientId();
String clientId = tokenRequest.getClientId();
if (clientId != null && !clientId.equals(pendingClientId)) {
throw new InvalidClientException("Client ID mismatch");
} else {
Map<String, String> combinedParameters = new HashMap(pendingOAuth2Request.getRequestParameters());
combinedParameters.putAll(parameters);
OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);
Authentication userAuth = storedAuth.getUserAuthentication();
return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);
}
}
}
}
}
}

public interface AuthorizationCodeServices {
String createAuthorizationCode(OAuth2Authentication var1);

OAuth2Authentication consumeAuthorizationCode(String var1) throws InvalidGrantException;
}

ClientCredentialsTokenGranter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ClientCredentialsTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "client_credentials";
private boolean allowRefresh;

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
// 转发请求到父类执行,只是判断是否能够获取 刷新token
OAuth2AccessToken token = super.grant(grantType, tokenRequest);
if (token != null) {
// 根据请求头的token
DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken((OAuth2AccessToken)token);
if (!this.allowRefresh) {
norefresh.setRefreshToken((OAuth2RefreshToken)null);
}
token = norefresh;
}
return (OAuth2AccessToken)token;
}
}

ResourceOwnerPasswordTokenGranter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "password";
private final AuthenticationManager authenticationManager;
// 密码模式:直接调用密码模式进行验证,封装OAuth2Authentication
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String username = (String)parameters.get("username");
String password = (String)parameters.get("password");
parameters.remove("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
((AbstractAuthenticationToken)userAuth).setDetails(parameters);
Authentication userAuth;
// 通过未验证 Authentication 获取 验证后的Authentication
userAuth = this.authenticationManager.authenticate(userAuth);

if (userAuth != null && userAuth.isAuthenticated()) {
// 通过Facotry,将Client信息和Request信息封装为 OAuthReqeust
OAuth2Request storedOAuth2Request = this.getRequestFactory().createOAuth2Request(client, tokenRequest);
// 床架OAuth2Authentication 返回
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
}

Service

DefaultTokenServices

多种Service的实现和复合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DefaultTokenServices implements AuthorizationServerTokenServices, 
ResourceServerTokenServices, ConsumerTokenServices, InitializingBean {
// 设置refreshToken的有效时间
private int refreshTokenValiditySeconds = 2592000;
// 设置accessToken的有效时间
private int accessTokenValiditySeconds = 43200;
// 是否支持refreshToken模式
private boolean supportRefreshToken = false;
// refreshToken是否可重用
private boolean reuseRefreshToken = true;
// 用于token持久化保存措施 (内存,数据库,缓存)
private TokenStore tokenStore;
// 用于获取ClientDetail通过 id ## loadClientByClientId()
private ClientDetailsService clientDetailsService;
// token增强处理 -- (设置参数,加密方式等)
private TokenEnhancer accessTokenEnhancer;
// 验证流程的调用 (用户名密码。短信)
private AuthenticationManager authenticationManager;
}

image-20230411181723899

image-20230414085922878

AuthorizationServerTokenServices
1
2
3
4
5
6
7
8
9
// 授权token -- 需要 OAuth2Authentication作为参数(封装好的信息)
public interface AuthorizationServerTokenServices {
// 创建访问token
OAuth2AccessToken createAccessToken(OAuth2Authentication var1) throws AuthenticationException;
// 更新访问token
OAuth2AccessToken refreshAccessToken(String var1, TokenRequest var2) throws AuthenticationException;
// 获取访问token
OAuth2AccessToken getAccessToken(OAuth2Authentication var1);
}
ResourceServerTokenServices
ConsumerTokenServices

AuthorizationCodeTokenServices

OAuth2RequestFactory

1
2
3
4
5
6
7
8
9
10
11
12
// 请求工厂-- 包装请求,访问API接口
public interface OAuth2RequestFactory {
AuthorizationRequest createAuthorizationRequest(Map<String, String> var1);

OAuth2Request createOAuth2Request(AuthorizationRequest var1);

OAuth2Request createOAuth2Request(ClientDetails var1, TokenRequest var2);

TokenRequest createTokenRequest(Map<String, String> var1, ClientDetails var2);

TokenRequest createTokenRequest(AuthorizationRequest var1, String var2);
}

信息封装

Authentication

OAuth2Authentication

1
2
3
4
5
6
7
8
public class OAuth2Authentication extends AbstractAuthenticationToken {

private static final long serialVersionUID = -4809832298438307309L;
// 请求信息
private final OAuth2Request storedRequest;
// 用户信息
private final Authentication userAuthentication;
}

PreAuthenticatedAuthenticationToken

1
2
3
4
5
public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 500L;
private final Object principal;
private final Object credentials;
}

BeaseRequest - 封装请求

1
2
3
4
5
6
7
abstract class BaseRequest implements Serializable {
// 客户端id
private String clientId;
private Set<String> scope = new HashSet();
// 请求参数 (请求头 + 请求体)
private Map<String, String> requestParameters = Collections.unmodifiableMap(new HashMap());
}

image-20230411174705602

TokenRequest

1
2
3
4
5
6
7
public class TokenRequest extends BaseRequest {
// 模式
private String grantType;
}
public class ImplicitTokenRequest extends TokenRequest {
private OAuth2Request oauth2Request;
}

AuthorizationRequest

认证请求

1
2
3
4
5
6
7
8
9
10
public class AuthorizationRequest extends BaseRequest implements Serializable {
private Map<String, String> approvalParameters = Collections.unmodifiableMap(new HashMap());
private String state;
private Set<String> responseTypes = new HashSet();
private Set<String> resourceIds = new HashSet();
private Collection<? extends GrantedAuthority> authorities = new HashSet();
private boolean approved = false;
private String redirectUri;
private Map<String, Serializable> extensions = new HashMap();
}

OAuth2Request

1
2
3
4
5
6
7
8
9
10
11
public class OAuth2Request extends BaseRequest implements Serializable {

private Set<String> resourceIds;
// 权限集合
private Collection<? extends GrantedAuthority> authorities;
private boolean approved;
private TokenRequest refresh;
private String redirectUri;
private Set<String> responseTypes;
private Map<String, Serializable> extensions;
}

Token信息封装 (返回值)

OAuth2AccessToken

/oauth/token

1、refresh_token 模式

2、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 管理
public interface OAuth2AccessToken {
String BEARER_TYPE = "Bearer";
String OAUTH2_TYPE = "OAuth2";
String ACCESS_TOKEN = "access_token";
String TOKEN_TYPE = "token_type";
String EXPIRES_IN = "expires_in";
String REFRESH_TOKEN = "refresh_token";
String SCOPE = "scope";

Map<String, Object> getAdditionalInformation();

Set<String> getScope();

OAuth2RefreshToken getRefreshToken();

String getTokenType();

boolean isExpired();

Date getExpiration();

int getExpiresIn();

String getValue();
}

DefaultOAuth2AccessToken

1
2
3
4
5
6
7
8
9
10
11
public class DefaultOAuth2AccessToken implements Serializable, OAuth2AccessToken {
private String value;
// 过期实践
private Date expiration;
// 类型
private String tokenType;
private OAuth2RefreshToken refreshToken;
private Set<String> scope;
// 额外的信息
private Map<String, Object> additionalInformation;
}

OAuth2Authentication

模式:

authorization_code 、 implicit、password(ResourceOwnerPasswordTokenGranter)

1
2
3
4
5
public class OAuth2Authentication extends AbstractAuthenticationToken {
private static final long serialVersionUID = -4809832298438307309L;
private final OAuth2Request storedRequest;
private final Authentication userAuthentication;
}

异常处理

AccessDeniedHandler

拒绝访问

1
2
3
public interface AccessDeniedHandler {
void handle(HttpServletRequest var1, HttpServletResponse var2, AccessDeniedException var3) throws IOException, ServletException;
}

image-20230416143518588

AuthenticationFailureHandler

校验失败

1
2
3
public interface AuthenticationFailureHandler {
void onAuthenticationFailure(HttpServletRequest var1, HttpServletResponse var2, AuthenticationException var3) throws IOException, ServletException;
}

image-20230416143708979

SSO - Sigle Sign On

问:如何实现同域单点登录和跨域单点登录 session,jwt,cookie

1
2
3
4
5
6
7
8
9
10
同域单点登录和跨域单点登录可以通过如下技术实现:

Session:同域单点登录可以使用Session实现,即将用户的身份信息存放在服务器端的session中。当用户第一次访问系统时,系统会为其创建一个session,并在session中保存用户的身份信息。之后如果再访问其他同域的子系统,就可以直接使用该session来判断用户是否已经登录。
(对于单机,session可以;对于分布式,需要采用分布式session + Redis实现信息共享和传递)

Cookie:同域单点登录也可以使用Cookie实现,将用户的身份信息存储在cookie中,而不是服务器端的session中。可以将cookie设置为domain属性为主域名,在同一主域名下的所有子系统均可进行访问获取cookie中的用户信息,然后判断用户是否已经登录。

JWT:跨域单点登录可以使用JWT(JSON Web Token)实现,它是一种安全的、自包含的令牌,可以在不同的系统之间传递和验证用户信息。用户在登录认证成功后,将会生成一个JWT,然后将此令牌发送到其他系统中请求资源。其他系统从JWT中解码并验证签名,从而知道这个请求是由已认证的用户发出的,并且可以查看令牌中携带的用户信息。

总结来说,使用Session和Cookie等技术实现同域单点登录,使用JWT则更加适合实现跨域单点登录。同时,这些技术各有优劣,具体方案的选择要根据实际业务场景需求、安全性要求等综合考虑。

问:分布式系统实现同域和跨域单点登录的方式

1
2
3
4
5
6
7
在分布式系统中,由于多个系统之间可能位于不同的域名下,同域和跨域单点登录实现方式会有所不同。

同域单点登录:在分布式系统中,同一个主域名下的所有子系统可以通过共享Session或Cookie来实现同域单点登录。具体而言,主域名下的某一系统认证成功后,将用户信息保存到共享Session或Cookie中,并设置为主域名,其他子系统就可以从Session或Cookie中获取用户信息,从而实现同域单点登录。

跨域单点登录:在分布式系统中,跨域单点登录可以通过OAuth2或OpenID Connect等协议来实现。这些协议都采用了JWT(JSON Web Token)令牌,使得各个系统之间能够安全、可靠地传递身份信息。具体而言,在使用OAuth2或OpenID Connect时,用户在进行身份认证时,授权服务器会生成一个JWT,包含了经过加密的用户信息和授权信息,然后将该令牌返回给客户端。客户端再将该令牌带到其他系统中,其他系统就可以根据JWT中的信息判断该用户是否已认证。

需要注意的是,跨域单点登录的实现过程需要确保令牌的安全性,例如加密、签名等措施的保护。同时,也需要保证各个系统之间的协议兼容性,确保系统之间能够顺畅地交换信息。

CAS实现单点登录

基于CAS实现SSO单点登录-阿里云开发者社区 (aliyun.com)

问:sso框架CAS实现单点登录的具体实现逻辑,附加代码,原理

CAS(Central Authentication Service)是一种支持单点登录的开源框架,它的实现原理是基于代理机制实现的。

具体的实现逻辑如下:

  1. 用户首次访问需要认证的应用系统A,系统A检测到用户未登录,跳转至CAS服务器进行用户认证请求。
  2. CAS服务器接收到认证请求后,判断当前会话中是否存在认证凭证。如果不存在,则显示登录页面供用户输入用户名和密码进行认证;否则使用已存在的凭证信息认证用户身份。
  3. 用户在CAS服务器上成功认证后,CAS服务器返回一个加密的ticket给应用系统A。
  4. 应用系统A通过ticket向CAS服务器发起验证请求,用以确认ticket的有效性。用户访问其他应用系统B,同样需要进行上述流程。
  5. 如果所有的应用系统都验证通过,用户就可以在整个系统内无需重复登录,即实现了单点登录。

代码实现:

对于Java Web应用来说,可通过引入cas-client-corecas-client-integration-filter两个依赖,再配置web.xml文件来实现CAS客户端的集成。

对于Spring Boot应用,可以通过spring-boot-starter-securityspring-security-cas两个依赖进行集成,同时配置相应的yml或properties文件即可。

原理:

CAS框架的实现原理主要基于以下核心机制:Ticket、Service和Validation。

  • Ticket:表示凭证,用来标识已经认证的用户身份。
  • Service:表示需要受到保护的服务,例如web应用程序、WebService等。
  • Validation:验证机制,主要用于验证ticket是否有效。

CAS客户端只需在需要协议支持的请求中附加ticket参数,并向CAS服务器发起验证请求,从而实现服务与认证分离,使系统整体更加灵活

问:CAS和JWT实现单点登录的优势和区别

CAS和JWT都是用于实现单点登录的技术,它们各自有不同的优势和特点。

  1. CAS的优势:
  • CAS实现单点登录时采用了代理机制,可以支持多种协议(如CAS、OAuth、SAML等)。因此,CAS比较适合大型系统或者需要与其他系统集成的场景。
  • CAS支持用户退出功能,可以使得用户退出所有已登录的应用系统,提高用户体验和安全性。
  • CAS具有很好的扩展性,便于二次开发和定制化改造。
  1. JWT的优势:
  • JWT使用JSON数据结构作为认证凭据,相对于Token,它更加轻量级和灵活,可以轻松携带一些个性化的信息(如用户角色、权限等)。
  • JWT的运行速度快,验证简单,在微服务和API网关中非常实用。
  • JWT可以避免对CAS服务器的依赖,避免了单点故障等问题。

区别:

  • 实现方面:CAS基于代理机制,而JWT主要是通过Token机制来实现。两者略有不同的认证流程。
  • 认证凭据:CAS的认证凭据是Ticket,而JWT的认证凭据是Token。
  • 安全性:CAS采用了一系列的安全机制和预防措施;而JWT因为Token数据存储在客户端,一旦被篡改或者泄露,会导致较大的风险。
  • 扩展性:CAS可以定制化改造和扩展,更适用于大型系统;而JWT则更适用于轻量级应用和API网关。

第三方登录

第三方登录:微信扫码登录(OAuth2.0 - 腾讯云开发者社区-腾讯云 (tencent.com)

步骤:

创建开发者,绑定应用,获取ClientId和ClientSecret

关键参数:

1、请求code:ClientId, redirect_uri, response_type = code state=xx

返回code:code, state

2、请求access_token : ClientId, ClientSecret, grant_tpye = “authorization_code” , state, code

返回:access_token, refresh_token, openid,

3、请求用户信息:access_token, openid

请求url说明

  第三方使用网站应用授权登录前请注意已获取相应网页授权作用域(scope=snsapi_login),则可以通过在PC端打开以下链接:

1
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

参数说明

参数 是否必须 说明
appid 应用唯一标识(前面认证网页应用中获得)
redirect_uri 重定向地址,需要进行UrlEncode(前面认证网页应用中获得)
response_type 填code
scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
state 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验

返回说明

 用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数

1
redirect_uri?code=CODE&state=STATE

复制

 若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数

1
redirect_uri?state=STATE

img


Spring Security Oauth2
http://example.com/2023/06/01/Spring家族/Spring Security Oauth2/
作者
where
发布于
2023年6月1日
许可协议