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。、
(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 密钥,不另作说明
1.2、Resource Owner Password Credentials Flow 直接使用用户密码,不另作说明
1.3、Authorization Code Flow *授权码模式*
流程简单表述为,当用户访问应用服务器
***(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等
流程简单表述为,当用户访问应用服务
***(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** **
获取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 请求参数:
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 { 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<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<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<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<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
参考QQ QQ是中国腾讯公司推出的一款即时通讯软件,它提供了第三方网站或应用通过OAuth 2.0进行授权登陆的功能。以下是QQ OAuth2.0授权的基本流程,包括重定向等过程:
第三方网站或应用与QQ互联平台注册应用,获取App ID和App Key。
用户在第三方网站或应用上点击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
:授权范围,可选。
用户点击授权链接,浏览器跳转到QQ登录页面。用户输入QQ账号和密码,进行登录。
用户登录成功后,QQ服务器将浏览器重定向到第三方网站或应用在步骤2中提供的redirect_uri
,并附带一个授权码(code),例如:
1 ***/?code=AUTHORIZATION_CODE&state =STATE
第三方网站或应用收到授权码后,将授权码发送到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
一致。
QQ服务器返回访问令牌和刷新令牌,例如:
1 2 3 4 5 { "access_token" : "ACCESS_TOKEN" , "expires_in" : 7776000 , "refresh_token" : "REFRESH_TOKEN" }
第三方网站或应用使用访问令牌调用QQ互联平台API,获取用户信息等操作。
当访问令牌过期时,第三方网站或应用可以使用刷新令牌获取新的访问令牌,请求的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
:刷新令牌。
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 var encodedString = base64UrlEncode (header) + '.' + base64UrlEncode (payload);var signature = HMACSHA256 (encodedString, 'secret' );
secret – 私钥
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
存储位置 三者都是应用在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:
结构简单。cookie是一种基于文本的轻量结构,包含简单的键值对。
数据持久。虽然客户端计算机上cookie的持续时间取决于客户端上的cookie过期处理和用户干预,cookie通常是客户端上持续时间最长的数据保留形式。
大小受到限制。大多数浏览器对 cookie 的大小有 4096 字节的限制,尽管在当今新的浏览器和客户端设备版本中,支持 8192 字节的 cookie 大小已愈发常见。
非常不安全。cookie将数据裸露在浏览器中,这样大大增大了数据被盗取的风险,所有我们不应该将中要的数据放在cookie中,或者将数据加密处理。
容易被csrf攻击。可以设置csrf_token来避免攻击。
session:
session的信息存储在服务端,相比于cookie就在一定程度上加大了数据的安全性;相比于jwt方便进行管理,也就是说当用户登录和主动注销,只需要添加删除对应的session就可以,这样管理起来很方便。
session存储在服务端,这就增大了服务器的开销,当用户多的情况下,服务器性能会大大降低。
因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,会限制负载均衡和集群水平拓展的能力。
JWT:
因为json的通用性,jwt可以支持跨语言请求,像JAVA,JavaScript,PHP等很多语言都可以使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。
不需要在服务端保存会话信息, 利于服务器横向拓展。
登录状态信息续签问题。比如设置token的有效期为一个小时,那么一个小时后,如果用户仍然在这个web应用上,这个时候当然不能指望用户再登录一次。目前可用的解决办法是在每次用户发出请求都返回一个新的token,前端再用这个新的token来替代旧的,这样每一次请求都会刷新token的有效期。但是这样,需要频繁的生成token。另外一种方案是判断还有多久这个token会过期,在token快要过期时,返回一个新的token。
用户主动注销。JWT并不支持用户主动退出登录,客户端在别处使用token仍然可以正常访问。为了支持注销,我的解决方案是在注销时将该token加入到服务器的redis黑名单中。
JWT与OAuth的区别
> 这两个概念总有人用混淆,所以一起介绍了。
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 { 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 authRequest = new UsernamePasswordAuthenticationToken (username, password); this .setDetails(request, authRequest); 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 private List<AuthenticationProvider> providers;public Authentication authenticate (Authentication authentication) throws AuthenticationException { Class<? extends Authentication > toTest = authentication.getClass(); while (var8.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var8.next(); 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; 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 { 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 ; 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 { protected final UserDetails retrieveUser (String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 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; private Object credentials; 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。它们各自的作用如下:
Web Security: Web Security用于配置认证和授权规则,以及定义用户身份验证的方式。它主要用于保护应用程序的用户界面部分,即Web页面和URL路径。通过配置Web Security,你可以定义哪些URL需要进行身份验证,以及哪些用户角色可以访问特定的URL。
Web Security的配置通常包括以下内容:
定义用户身份验证的方式,例如基于表单登录、基于HTTP基本认证、基于OAuth2等。
配置用户登录页面和登录成功后的跳转页面。
配置用户注销的处理方式。
定义访问控制规则,包括允许或拒绝特定角色的用户访问特定的URL路径。
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:
配置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" ) .anyRequest().authenticated() .and() .formLogin() .loginPage("/login" ) .defaultSuccessUrl("/home" ) .permitAll() .and() .logout() .logoutUrl("/logout" ) .logoutSuccessUrl("/login?logout" ) .permitAll(); } }
上述配置中,我们使用authorizeRequests()
方法定义了访问控制规则。antMatchers()
方法用于指定URL路径的匹配规则,permitAll()
表示允许所有用户访问,hasRole()
表示需要特定角色才能访问。anyRequest().authenticated()
表示其他路径需要身份验证。
formLogin()
方法配置了使用表单登录,loginPage()
指定了登录页面的URL,defaultSuccessUrl()
指定了登录成功后的跳转URL。
logout()
方法配置了注销功能,logoutUrl()
指定了注销URL,logoutSuccessUrl()
指定了注销成功后的跳转URL。
配置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() .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; }
AuthenticationManagerBuilder
创建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; private List<AuthenticationProvider> authenticationProviders = new ArrayList (); private UserDetailsService defaultUserDetailsService; private Boolean eraseCredentials; private AuthenticationEventPublisher eventPublisher; protected ProviderManager performBuild () throws Exception { 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; } } 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> { B authenticationProvider (AuthenticationProvider var1) ; }
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; }
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> { 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); } } } }public interface ObjectPostProcessor <T> { <O extends T > O postProcess (O var1) ; }
WebSecurity
这里没有使用
适配器类,用于管理配置类的注入
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() { } 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()); private String beanName; private Environment environment; private ServletContext servletContext; private FilterConfig filterConfig; private final Set<String> requiredProperties = new HashSet (4 ); }
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 { 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); return this .getAuthenticationManager().authenticate(authRequest); } } }
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; 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; } } } 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; } } }
AuthenticationProvider 具体的校验逻辑的实现,通过support()方法,可以专门处理一种校验方式 。
Sms短信校验,用户名密码登录校验,
1 2 3 4 5 6 7 8 9 10 11 public interface AuthenticationProvider { Authentication authenticate (Authentication var1) throws AuthenticationException; 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 (); public Authentication authenticate (Authentication authentication) { UserDetails user = this .userCache.getUserFromCache(username); if (user == null ) { user = this .retrieveUser(username, (UsernamePasswordAuthenticationToken)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; }public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword" ; private PasswordEncoder passwordEncoder; private volatile String userNotFoundEncodedPassword; private UserDetailsService userDetailsService; protected final UserDetails retrieveUser (String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { UserDetails loadedUser = this .getUserDetailsService().loadUserByUsername(username); return loadedUser; }
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 private String usersByUsernameQuery = "select username,password,enabled from users where username = ?" ;protected List<UserDetails> loadUsersByUsername (String username) { 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()); } }
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 () ; }
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 public abstract class AbstractAuthenticationToken implements Authentication , CredentialsContainer { private final Collection<GrantedAuthority> authorities; private Object details; private boolean authenticated = false ; }
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 ; private final Object principal; private Object credentials; 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; }
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; } }
SecurityBuilder
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判断有无权限。
配置类Configuration 过滤器链 spring cloud OAuth2资源服务器的过滤器链路浅析 - 简书 (jianshu.com)
SecurityBuilder - 重点 用于构建 WebSecurity 的Filter 或者是 HttpSecurity的 FilterChain
1 2 3 4 public interface SecurityBuilder <O> { O build () throws Exception; }
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; } } protected abstract O doBuild () throws Exception; }
持有 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 public abstract class AbstractConfiguredSecurityBuilder <O, B extends SecurityBuilder <O>> extends AbstractSecurityBuilder <O> { private final LinkedHashMap<Class<? extends SecurityConfigurer <O, B>>, List<SecurityConfigurer<O, B>>> configurers; private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing; private final Map<Class<? extends Object >, Object> sharedObjects; private final boolean allowConfigurersOfSameType; private AbstractConfiguredSecurityBuilder.BuildState buildState; protected abstract O performBuild () throws Exception; protected final O doBuild () throws Exception { synchronized (this .configurers) { this .buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING; this .beforeInit(); this .init(); this .buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING; this .beforeConfigure(); this .configure(); this .buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING; O result = this .performBuild(); this .buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT; return result; } } 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 ); } } 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 public final class WebSecurity extends AbstractConfiguredSecurityBuilder <Filter, WebSecurity> 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; 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(); 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 = 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 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; private FilterComparator comparator; protected DefaultSecurityFilterChain performBuild () throws Exception { Collections.sort(this .filters, this .comparator); return new DefaultSecurityFilterChain (this .requestMatcher, this .filters); } }public final class DefaultSecurityFilterChain implements SecurityFilterChain { private final RequestMatcher requestMatcher; private final List<Filter> filters; }
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) ; } 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; 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); } } while (matcher.matches(request)); this .logger.debug("Did not match" ); return false ; } } 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 ; } }
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; }
持有 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> { private B securityBuilder; private SecurityConfigurerAdapter.CompositeObjectPostProcessor objectPostProcessor = new SecurityConfigurerAdapter .CompositeObjectPostProcessor(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public final class AuthorizationServerSecurityConfigurer extends SecurityConfigurerAdapter <DefaultSecurityFilterChain, HttpSecurity> { private AuthenticationEntryPoint authenticationEntryPoint; private AccessDeniedHandler accessDeniedHandler = new OAuth2AccessDeniedHandler (); private PasswordEncoder passwordEncoder; private String realm = "oauth2/client" ; private boolean allowFormAuthenticationForClients = false ; private String tokenKeyAccess = "denyAll()" ; private String checkTokenAccess = "denyAll()" ; private boolean sslOnly = false ; }
一个空接口,用于与SecurityConfigurer区别 —- 用于配置WebSecurity
[注解](#注解引入配置类 - 入口)
作用 – 用于配置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; 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; 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(); } }
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; private List<ResourceServerConfigurer> configurers = Collections.emptyList(); @Autowired(required = false) private AuthorizationServerEndpointsConfiguration endpoints;
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 private List<AuthorizationServerConfigurer> configurers = Collections.emptyList(); @Autowired private ClientDetailsService clientDetailsService; }
f
WebSecurityConfigurerAdapter的子类 — (WebSecurity)
被ResourceServerConfiguration 和 AuthorizationServerSecurityConfiguration 持有
用于配置信息
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 { void configure (ResourceServerSecurityConfigurer resources) throws Exception; void configure (HttpSecurity http) throws Exception; }
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(); } }
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 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(); } } }
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 public interface AuthorizationServerConfigurer { void configure (AuthorizationServerSecurityConfigurer security) throws Exception; void configure (ClientDetailsServiceConfigurer clients) throws Exception; void configure (AuthorizationServerEndpointsConfigurer endpoints) throws Exception; }
适配器
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 package org.springframework.boot.autoconfigure.security.oauth2.authserver;@Configuration @ConditionalOnClass({EnableAuthorizationServer.class}) @ConditionalOnMissingBean({AuthorizationServerConfigurer.class}) @ConditionalOnBean({AuthorizationServerEndpointsConfiguration.class}) @EnableConfigurationProperties({AuthorizationServerProperties.class}) @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;
注解引入配置类 - 入口 @EnableWebSecurity 引入WebSecurityConfiguration 和 SpringWebMvcImportSelector 配置类
打开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 ; }@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 { private WebSecurity webSecurity; private Boolean debugEnabled; 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() { } 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 ) private Map<String, ResourceServerTokenServices> tokenServices; @Autowired private ApplicationContext context; private List<ResourceServerConfigurer> configurers = Collections.emptyList(); @Autowired( required = false ) private AuthorizationServerEndpointsConfiguration endpoints; }
@EnableAuthorizationServer <span id = “@EnableAuthorizationServer2”>@EnableAuthorizationServer
WebSecurityConfigurerAdapter管理这两个类
引入AuthorizationServerSecurityConfiguration 和 AuthorizationServerEndpointsConfiguration 配置类
– 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); } }
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 private List<AuthorizationServerConfigurer> configurers = Collections.emptyList(); }
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" ; 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; private Map<String, Object> additionalInformation = Collections.emptyMap(); }
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; }
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 public interface TokenStore { OAuth2Authentication readAuthentication (OAuth2AccessToken token) ; OAuth2Authentication readAuthentication (String token) ; void storeAccessToken (OAuth2AccessToken token, OAuth2Authentication authentication) ; OAuth2AccessToken readAccessToken (String tokenValue) ; void removeAccessToken (OAuth2AccessToken token) ; void storeRefreshToken (OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) ; OAuth2RefreshToken readRefreshToken (String tokenValue) ; OAuth2Authentication readAuthenticationForRefreshToken (OAuth2RefreshToken token) ; void removeRefreshToken (OAuth2RefreshToken token) ; void removeAccessTokenUsingRefreshToken (OAuth2RefreshToken refreshToken) ; OAuth2AccessToken getAccessToken (OAuth2Authentication authentication) ; Collection<OAuth2AccessToken> findTokensByClientIdAndUserName (String clientId, String userName) ; Collection<OAuth2AccessToken> findTokensByClientId (String clientId) ; }
TokenEnhancer 生成token时用于增强token
1 2 3 4 5 6 7 8 9 10 public interface TokenEnhancer { 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" ; Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication); OAuth2AccessToken extractAccessToken (String value, Map<String, ?> map) ; 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 public class JwtAccessTokenConverter implements TokenEnhancer , AccessTokenConverter, InitializingBean { public static final String TOKEN_ID = AccessTokenConverter.JTI; 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; }
实现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 (); private TokenExtractor tokenExtractor = new BearerTokenExtractor (); private AuthenticationEventPublisher eventPublisher = new OAuth2AuthenticationProcessingFilter .NullEventPublisher(); private boolean stateless = true ; }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 确认访问
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 (); private TokenGranter tokenGranter; private ClientDetailsService clientDetailsService; private OAuth2RequestFactory oAuth2RequestFactory; private OAuth2RequestFactory defaultOAuth2RequestFactory; 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; } } }public interface ClientDetailsService { ClientDetails loadClientByClientId (String var1) throws ClientRegistrationException; }
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) { 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()); 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); oauth2RequestValidator.validateScope(authorizationRequest, client); authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication) principal); boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal); authorizationRequest.setApproved(approved); if (authorizationRequest.isApproved()) { if (responseTypes.contains("token" )) { return getImplicitGrantResponse(authorizationRequest); } if (responseTypes.contains("code" )) { return new ModelAndView (getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal)); } } 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 { String createAuthorizationCode (OAuth2Authentication authentication) ; OAuth2Authentication consumeAuthorizationCode (String code) throws InvalidGrantException; }
TokenEndpoint 简单概括下来,整个生成token 的逻辑如下:
1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置的客户端信息
3、 通过客户端信息生成 TokenRequest 对象
4、 将步骤3获取到的 TokenRequest 作为TokenGranter.grant() 方法参照 生成 OAuth2AccessToken 对象(即token)
5、 返回 token
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 { private ResourceServerTokenServices resourceServerTokenServices; private AccessTokenConverter accessTokenConverter = new CheckTokenAccessTokenConverter (); @RequestMapping(value = "/oauth/check_token") @ResponseBody public Map<String, ?> checkToken(@RequestParam("token") String value) { 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 { OAuth2Authentication loadAuthentication (String accessToken) throws AuthenticationException, InvalidTokenException; OAuth2AccessToken readAccessToken (String accessToken) ; }
授权模式 TokenGranter 根据不同模式 生成token,即:通过认证后的 authentication 信息 (再封装为 toekn request),封装为对应模式的 token
authorization_code 授权码认证
client_credentials 客户端认证
password 密码认证
implicit 隐式授权认证
refresh_token 刷新密钥
1 2 3 4 public interface TokenGranter { OAuth2AccessToken grant (String var1, TokenRequest var2) ; }
1 2 3 4 5 6 7 8 9 RefreshTokenGranter : 更新token模式 AuthorizationCodeTokenGranter :授权码模式 ImplicitTokenGranter : 简化模式// 调用 UsernamePasswordAuthenticationProvider的执行流程 ResourceOwnerPasswordTokenGranter :密码模式 ClientCredentialsTokenGranter :客户端凭证模式
执行链
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 ```javapublic abstract class AbstractTokenGranter implements TokenGranter { private final AuthorizationServerTokenServices tokenServices; private final ClientDetailsService clientDetailsService; private final OAuth2RequestFactory requestFactory; private final String grantType; 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); } return this .getAccessToken(client, tokenRequest); } } protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { return this .tokenServices.createAccessToken(this .getOAuth2Authentication(client, tokenRequest)); } protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { OAuth2Request storedOAuth2Request = this .requestFactory.createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, (Authentication)null ); } } ```` #### RefreshTokenGranter ```javapublic class RefreshTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "refresh_token" ; protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { String refreshToken = (String)tokenRequest.getRequestParameters().get ("refresh_token" ); 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" ; private final AuthorizationCodeServices authorizationCodeServices; 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) { OAuth2AccessToken token = super .grant(grantType, tokenRequest); if (token != null ) { 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; 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; userAuth = this .authenticationManager.authenticate(userAuth); if (userAuth != null && userAuth.isAuthenticated()) { OAuth2Request storedOAuth2Request = this .getRequestFactory().createOAuth2Request(client, tokenRequest); 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 { private int refreshTokenValiditySeconds = 2592000 ; private int accessTokenValiditySeconds = 43200 ; private boolean supportRefreshToken = false ; private boolean reuseRefreshToken = true ; private TokenStore tokenStore; private ClientDetailsService clientDetailsService; private TokenEnhancer accessTokenEnhancer; private AuthenticationManager authenticationManager; }
AuthorizationServerTokenServices 1 2 3 4 5 6 7 8 9 public interface AuthorizationServerTokenServices { OAuth2AccessToken createAccessToken (OAuth2Authentication var1) throws AuthenticationException; OAuth2AccessToken refreshAccessToken (String var1, TokenRequest var2) throws AuthenticationException; OAuth2AccessToken getAccessToken (OAuth2Authentication var1) ; }
ResourceServerTokenServices ConsumerTokenServices AuthorizationCodeTokenServices OAuth2RequestFactory 1 2 3 4 5 6 7 8 9 10 11 12 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 { private String clientId; private Set<String> scope = new HashSet (); private Map<String, String> requestParameters = Collections.unmodifiableMap(new HashMap ()); }
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; }
AuthenticationFailureHandler 校验失败
1 2 3 public interface AuthenticationFailureHandler { void onAuthenticationFailure (HttpServletRequest var1, HttpServletResponse var2, AuthenticationException var3) throws IOException, ServletException; }
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)是一种支持单点登录的开源框架,它的实现原理是基于代理机制实现的。
具体的实现逻辑如下:
用户首次访问需要认证的应用系统A,系统A检测到用户未登录,跳转至CAS服务器进行用户认证请求。
CAS服务器接收到认证请求后,判断当前会话中是否存在认证凭证。如果不存在,则显示登录页面供用户输入用户名和密码进行认证;否则使用已存在的凭证信息认证用户身份。
用户在CAS服务器上成功认证后,CAS服务器返回一个加密的ticket给应用系统A。
应用系统A通过ticket向CAS服务器发起验证请求,用以确认ticket的有效性。用户访问其他应用系统B,同样需要进行上述流程。
如果所有的应用系统都验证通过,用户就可以在整个系统内无需重复登录,即实现了单点登录。
代码实现:
对于Java Web应用来说,可通过引入cas-client-core
和cas-client-integration-filter
两个依赖,再配置web.xml文件来实现CAS客户端的集成。
对于Spring Boot应用,可以通过spring-boot-starter-security
和spring-security-cas
两个依赖进行集成,同时配置相应的yml或properties文件即可。
原理:
CAS框架的实现原理主要基于以下核心机制:Ticket、Service和Validation。
Ticket:表示凭证,用来标识已经认证的用户身份。
Service:表示需要受到保护的服务,例如web应用程序、WebService等。
Validation:验证机制,主要用于验证ticket是否有效。
CAS客户端只需在需要协议支持的请求中附加ticket参数,并向CAS服务器发起验证请求,从而实现服务与认证分离,使系统整体更加灵活
问:CAS和JWT实现单点登录的优势和区别
CAS和JWT都是用于实现单点登录的技术,它们各自有不同的优势和特点。
CAS的优势:
CAS实现单点登录时采用了代理机制,可以支持多种协议(如CAS、OAuth、SAML等)。因此,CAS比较适合大型系统或者需要与其他系统集成的场景。
CAS支持用户退出功能,可以使得用户退出所有已登录的应用系统,提高用户体验和安全性。
CAS具有很好的扩展性,便于二次开发和定制化改造。
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端打开以下链接:
参数说明
参数
是否必须
说明
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