Re:从零开始的Spring Security Oauth2(二)
本文开始从源码的层面,讲解一些Spring Security Oauth2的认证流程。本文较长,适合在空余时间段观看。且涉及了较多的源码,非关键性代码以…代替。
准备工作
首先开启debug信息:
logging:level:org.springframework: DEBUG
可以完整的看到内部的运转流程。
client模式稍微简单一些,使用client模式获取token
http://localhost:8080/oauth/token?client_id=client_1&client_secret=123456&scope=select&grant_type=client_credentials
由于debug信息太多了,我简单按照顺序列了一下关键的几个类:
ClientCredentialsTokenEndpointFilter
DaoAuthenticationProvider
TokenEndpoint
TokenGranter
@EnableAuthorizationServer
上一篇博客中我们尝试使用了password模式和client模式,有一个比较关键的endpoint:/oauth/token。从这个入口开始分析,spring security oauth2内部是如何生成token的。获取token,与第一篇文章中的两个重要概念之一有关,也就是AuthorizationServer与ResourceServer中的AuthorizationServer。
在之前的配置中
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {}
出现了AuthorizationServerConfigurerAdapter 关键类,他关联了三个重要的配置类,分别是
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {@Overridepublic void configure(AuthorizationServerSecurityConfigurer security <1>) throws Exception {}@Overridepublic void configure(ClientDetailsServiceConfigurer clients <2>) throws Exception {}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints <3>) throws Exception {}}
<1> 配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
<2> 配置OAuth2的客户端相关信息
<3> 配置AuthorizationServerEndpointsConfigurer众多相关类,包括配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
我们逐步分析其中关键的类
客户端身份认证核心过滤器ClientCredentialsTokenEndpointFilter(掌握)
截取关键的代码,可以分析出大概的流程
在请求到达/oauth/token之前经过了ClientCredentialsTokenEndpointFilter这个过滤器,关键方法如下
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException, IOException, ServletException {...String clientId = request.getParameter("client_id");String clientSecret = request.getParameter("client_secret");...clientId = clientId.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,clientSecret);return this.getAuthenticationManager().authenticate(authRequest);}
顶级身份管理者AuthenticationManager(掌握)
用来从请求中获取client_id,client_secret,组装成一个UsernamePasswordAuthenticationToken作为身份标识,使用容器中的顶级身份管理器AuthenticationManager去进行身份认证(AuthenticationManager的实现类一般是ProviderManager。而ProviderManager内部维护了一个List,真正的身份认证是由一系列AuthenticationProvider去完成。而AuthenticationProvider的常用实现类则是DaoAuthenticationProvider,DaoAuthenticationProvider内部又聚合了一个UserDetailsService接口,UserDetailsService才是获取用户详细信息的最终接口,而我们上一篇文章中在内存中配置用户,就是使用了UserDetailsService的一个实现类InMemoryUserDetailsManager)。UML类图可以大概理解下这些类的关系,省略了授权部分。
图1 认证相关UML类图
可能机智的读者会发现一个问题,我前面一篇文章已经提到了client模式是不存在“用户”的概念的,那么这里的身份认证是在认证什么呢?debug可以发现UserDetailsService的实现被适配成了ClientDetailsUserDetailsService,这个设计是将client客户端的信息(client_id,client_secret)适配成用户的信息(username,password),这样我们的认证流程就不需要修改了。
经过ClientCredentialsTokenEndpointFilter之后,身份信息已经得到了AuthenticationManager的验证。接着便到达了
TokenEndpoint。
Token处理端点TokenEndpoint(掌握)
前面的两个ClientCredentialsTokenEndpointFilter和AuthenticationManager可以理解为一些前置校验,和身份封装,而这个类一看名字就知道和我们的token是密切相关的。
@FrameworkEndpoint
public class TokenEndpoint extends AbstractEndpoint {@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParamMap<String, String> parameters) throws HttpRequestMethodNotSupportedException {...String clientId = getClientId(principal);ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);//<1>...TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);//<2>...OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);//<3>...return getResponse(token);}private TokenGranter tokenGranter;
}
<1> 加载客户端信息
<2> 结合请求信息,创建TokenRequest
<3> 将TokenRequest传递给TokenGranter颁发token
省略了一些校验代码之后,真正的/oauth/token端点暴露在了我们眼前,其中方法参数中的Principal经过之前的过滤器,已经被填充了相关的信息,而方法的内部则是依赖了一个TokenGranter 来颁发token。其中OAuth2AccessToken的实现类DefaultOAuth2AccessToken就是最终在控制台得到的token序列化之前的原始类:
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();//getter,setter
}@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)
@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)
@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)
@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)public interface OAuth2AccessToken {public static String BEARER_TYPE = "Bearer";public static String OAUTH2_TYPE = "OAuth2";/*** The access token issued by the authorization server. This value is REQUIRED.*/public static String ACCESS_TOKEN = "access_token";/*** The type of the token issued as described in <a* href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-7.1">Section 7.1</a>. Value is case insensitive.* This value is REQUIRED.*/public static String TOKEN_TYPE = "token_type";/*** The lifetime in seconds of the access token. For example, the value "3600" denotes that the access token will* expire in one hour from the time the response was generated. This value is OPTIONAL.*/public static String EXPIRES_IN = "expires_in";/*** The refresh token which can be used to obtain new access tokens using the same authorization grant as described* in <a href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-6">Section 6</a>. This value is OPTIONAL.*/public static String REFRESH_TOKEN = "refresh_token";/*** The scope of the access token as described by <a* href="http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.3">Section 3.3</a>*/public static String SCOPE = "scope";...
}
一个典型的样例token响应,如下所示,就是上述类序列化后的结果:
{ "access_token":"950a7cc9-5a8a-42c9-a693-40e817b1a4b0", "token_type":"bearer", "refresh_token":"773a0fcd-6023-45f8-8848-e141296cb3cb", "expires_in":27036, "scope":"select"
}
TokenGranter(掌握)
先从UML类图对TokenGranter接口的设计有一个宏观的认识
图2 TokenGranter相关UML类图
TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,在debug过程中可以发现CompositeTokenGranter 内部就是在循环调用五种TokenGranter实现类的grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。
public class CompositeTokenGranter implements TokenGranter {private final List<TokenGranter> tokenGranters;public CompositeTokenGranter(List<TokenGranter> tokenGranters) {this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);}public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {for (TokenGranter granter : tokenGranters) {OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);if (grant!=null) {return grant;}}return null;}
}
五种类型分别是:
- ResourceOwnerPasswordTokenGranter ==> password密码模式
- AuthorizationCodeTokenGranter ==> authorization_code授权码模式
- ClientCredentialsTokenGranter ==> client_credentials客户端模式
- ImplicitTokenGranter ==> implicit简化模式
- RefreshTokenGranter ==>refresh_token 刷新token专用
以客户端模式为例,思考如何产生token的,则需要继续研究5种授权者的抽象类:AbstractTokenGranter
public abstract class AbstractTokenGranter implements TokenGranter {protected final Log logger = LogFactory.getLog(getClass());//与token相关的service,重点private final AuthorizationServerTokenServices tokenServices;//与clientDetails相关的service,重点private final ClientDetailsService clientDetailsService;//创建oauth2Request的工厂,重点private final OAuth2RequestFactory requestFactory;private final String grantType;...public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {...String clientId = tokenRequest.getClientId();ClientDetails client = clientDetailsService.loadClientByClientId(clientId);validateGrantType(grantType, client);logger.debug("Getting access token for: " + clientId);return getAccessToken(client, tokenRequest);}protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));}protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);return new OAuth2Authentication(storedOAuth2Request, null);}...
}
回过头去看TokenEndpoint中,正是调用了这里的三个重要的类变量的相关方法。由于篇幅限制,不能延展太多,不然没完没了,所以重点分析下AuthorizationServerTokenServices是何方神圣。
AuthorizationServerTokenServices(了解)
AuthorizationServer端的token操作service,接口设计如下:
public interface AuthorizationServerTokenServices {//创建tokenOAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;//刷新tokenOAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)throws AuthenticationException;//获取tokenOAuth2AccessToken getAccessToken(OAuth2Authentication authentication);}
在默认的实现类DefaultTokenServices中,可以看到token是如何产生的,并且了解了框架对token进行哪些信息的关联。
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken = null;if (existingAccessToken != null) {if (existingAccessToken.isExpired()) {if (existingAccessToken.getRefreshToken() != null) {refreshToken = existingAccessToken.getRefreshToken();// The token store could remove the refresh token when the// access token is removed, but we want to// be sure...tokenStore.removeRefreshToken(refreshToken);}tokenStore.removeAccessToken(existingAccessToken);}else {// Re-store the access token in case the authentication has changedtokenStore.storeAccessToken(existingAccessToken, authentication);return existingAccessToken;}}// Only create a new refresh token if there wasn't an existing one// associated with an expired access token.// Clients might be holding existing refresh tokens, so we re-use it in// the case that the old access token// expired.if (refreshToken == null) {refreshToken = createRefreshToken(authentication);}// But the refresh token itself might need to be re-issued if it has// expired.else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {refreshToken = createRefreshToken(authentication);}}OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);// In case it was modifiedrefreshToken = accessToken.getRefreshToken();if (refreshToken != null) {tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}
简单总结一下AuthorizationServerTokenServices的作用,他提供了创建token,刷新token,获取token的实现。在创建token时,他会调用tokenStore对产生的token和相关信息存储到对应的实现类中,可以是redis,数据库,内存,jwt。
总结
本篇总结了使用客户端模式获取Token时,spring security oauth2内部的运作流程,重点是在分析AuthenticationServer相关的类。其他模式有一定的不同,但抽象功能是固定的,只是具体的实现类会被相应地替换。阅读spring的源码,会发现它的设计中出现了非常多的抽象接口,这对我们理清楚内部工作流程产生了不小的困扰,我的方式是可以借助UML类图,先从宏观理清楚作者的设计思路,这会让我们的分析事半功倍。
下一篇文章重点分析用户携带token访问受限资源时,spring security oauth2内部的工作流程。即ResourceServer相关的类。
Re:从零开始的Spring Security Oauth2(二)相关推荐
- Spring Security Oauth2 (二) 代码 授权码模式
└─java└─claroja│ Springsecurityoauth2DemoApplication.java│├─config│ AuthorizationServerConfig.java│ ...
- Spring Security OAuth2 单点登录和登出
文章目录 1. 单点登录 1.1 使用内存保存客户端和用户信息 1.1.1 认证中心 auth-server 1.1.2 子系统 service-1 1.1.3 测试 1.2 使用数据库保存客户端和用 ...
- Spring Security OAuth2 入门
1. 概述 2. 引入 Spring Security OAuth2 依赖 3. 配置资源服务器 4. 配置授权服务器 4.1 授权码模式 Spring Security Setting 4.2 密码 ...
- 从零开始超详细的Spring Security OAuth2.0实现分布式系统授权(注册中心+网关+认证授权服务(JWT令牌验证)+资源调用服务)
文章目录 一.OAuth2.0 1.介绍 2.例子 3.执行流程 二.Spring Cloud Security OAuth2 1.环境介绍 2.认证流程 三.整合分布式项目 1.技术方案 2.项目结 ...
- java oauth sso 源码_基于Spring Security Oauth2的SSO单点登录+JWT权限控制实践
概 述 在前文<基于Spring Security和 JWT的权限系统设计>之中已经讨论过基于 Spring Security和 JWT的权限系统用法和实践,本文则进一步实践一下基于 Sp ...
- refreshtoken用mysql_「SpringCloud」 Spring Security OAuth2 Mysql管理在线Token
原标题:「SpringCloud」 Spring Security OAuth2 Mysql管理在线Token 前言:Spring Cloud 分布式中的登录如何可视化的管理目前下发的令牌.使用情况. ...
- 基于 Spring Security OAuth2和 JWT 构建保护微服务系统
我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的.尤其是前后端完全分离之后,我们的用户信息不一 ...
- Spring Security OAuth2整合JWT
文章目录 1. Spring Security 与 OAuth2 2. Spring Security OAuth2的配置和使用 ①:引入依赖 ②:配置 spring security ③:配置授权服 ...
- 《深入理解 Spring Cloud 与微服务构建》第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统
<深入理解 Spring Cloud 与微服务构建>第十八章 使用 Spring Security OAuth2 和 JWT 保护微服务系统 文章目录 <深入理解 Spring Cl ...
最新文章
- 从今天开始收集一些经典的算法。
- [zz]libvirt中CPU和内存的细粒度管理机制
- 云栖大会|感受万物数字化,体验千行视频化
- Activiti 中的 命令模式与责任链模式
- 计算机组成原理——Cache与主存的地址映射
- Origin绘制区间图,并添加折线
- TCP/IP详解学习笔记(11)-TCP交互数据流,成块数据流
- 一天一个Java基础——序列化
- BI工具那么多 该如何选择
- 统计图表类库--libchart使用简介
- MySQL基础知识及常见面试题整理
- Linux负载均衡--LVS
- 谁说小米手机拍照差?开启这3个模式,瞬间拍出单反的效果
- java开发季度绩效自评,季度绩效考核英文自我评价
- wordpress网站添加百度导航地图
- [失败]uuv_simulator在台式机_虚拟环境下会崩溃的问题[失败]
- android 不如 ios,安卓永远不如iOS运行流畅的根本原因
- 2021湖北技能高考文化综合成绩查询,2021年湖北省技能高考文化综合考试大纲.pdf...
- FlashFXP上传下载
- passwd: Have exhausted maximum number of retries for servic、ssh用普通用户登录输入密码正确但是登录却提示被拒绝问题解决,su到root报错
热门文章
- 20189217 2018-2019-2 《密码与安全新技术专题》第1周作业
- Zabbix监控web基本配置
- [LeetCode刷题笔记]714 - 买卖股票的最佳时机含手续费(C++/Python3/Java/动态规划/贪心)
- rono在oracle的作用_Oracle服务的作用
- 多旋翼无人机组合导航系统-多源信息融合算法(Matlab代码实现)
- 如何活让自己活得充实:其实很简单,把脑子里想的东西,变成行动,就能立刻充实起
- Win11系统默认用户名怎么进行修改教学
- Python笔记: 执行run.py文件
- 模拟集成电路设计与分析——全差分放大器
- 尚G谷大前端视频教程