这一篇我们继续深入 SpringSecurity , 看看其 OAuth2.0 的流程逻辑.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OAuth 包 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler myAuthenctiationFailureHandler;
@Autowired
private AuthorizationServerEndpointsConfiguration endpoints;
@Autowired
private UserService userService;
@Autowired
private ClientDetailsService clientDetailsService;
@Bean
public AuthenticationManager authenticationManagerBean(DataSource dataSource) throws Exception {
OAuth2AuthenticationManager authenticationManager = new OAuth2AuthenticationManager();
authenticationManager.setTokenServices(new DefaultTokenServices());
authenticationManager.setClientDetailsService(new JdbcClientDetailsService(dataSource));
return authenticationManager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//该方法用于用户认证,此处添加内存用户,并且指定了权限
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
@Autowired
public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
for (AuthorizationServerConfigurer configurer : configurers) {
configurer.configure(clientDetails);
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
configure(configurer);
http.apply(configurer);
String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
String authorizeEndpointPath = handlerMapping.getServletPath("/oauth/authorize");
String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
}
// PS : 注意 , OAuth 本身有一个 WebSecurityConfigurerAdapter ,我这里选择覆盖自定义
http.authorizeRequests()
.antMatchers("/test/**").permitAll()
.antMatchers("/before/**").permitAll()
.antMatchers("/index").permitAll()
.antMatchers(authorizeEndpointPath).authenticated()
.antMatchers(tokenEndpointPath).fullyAuthenticated()
.antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
.antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
.anyRequest().authenticated() //其它请求都需要校验才能访问
.and()
.requestMatchers()
// .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.formLogin()
.loginPage("/login") //定义登录的页面"/login",允许访问
.defaultSuccessUrl("/home") //登录成功后默认跳转到"list"
.successHandler(myAuthenticationSuccessHandler).failureHandler(myAuthenctiationFailureHandler).permitAll().and()
.logout() //默认的"/logout", 允许访问
.logoutSuccessUrl("/index")
.permitAll();
http.addFilterBefore(new BeforeFilter(), UsernamePasswordAuthenticationFilter.class);
http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/**/*.js", "/lang/*.json", "/**/*.css", "/**/*.js", "/**/*.map", "/**/*.html", "/**/*.png");
}
protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
for (AuthorizationServerConfigurer configurer : configurers) {
configurer.configure(oauthServer);
}
}
}
Resource 资源配置
@Configuration
@EnableResourceServer
public class ResServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenStore(tokenStore).resourceId("resourceId");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers()
.antMatchers("/user", "/res/**")
.and()
.authorizeRequests()
.antMatchers("/user", "/res/**")
.authenticated();
}
}
OAuthConfig 专属属性
@Configuration
@EnableAuthorizationServer
@Order(2)
public class OAuthConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
@Lazy
private AuthenticationManager authenticationManager;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean
public JdbcClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
public ApprovalStore approvalStore() {
return new JdbcApprovalStore(dataSource);
}
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(jdbcClientDetailsService());
}
//检查token的策略
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.tokenKeyAccess("isAuthenticated()");
security.checkTokenAccess("permitAll()");
}
//OAuth2的主配置信息
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// .approvalStore(approvalStore())
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices())
.tokenStore(tokenStore());
}
}
详见项目
请求方式
http://localhost:8080/security/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=app
https://www.baidu.com/?code=jYgDO3
AccessToken
var settings = {
"url": "http://localhost:8080/security/oauth/token",
"method": "POST",
"timeout": 0,
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"data": {
"grant_type": "authorization_code",
"client_id": "client",
"client_secret": "secret",
"code": "CFUFok",
"redirect_uri": "http://www.baidu.com"
}
};
$.ajax(settings).done(function (response) {
console.log(response);
});
// 失败
{
"error": "invalid_grant",
"error_description": "Invalid authorization code: CFUFok"
}
// 成功
{
"access_token": "c0955d7f-23fb-4ca3-8a52-c715867cbef2",
"token_type": "bearer",
"refresh_token": "55f53af0-1133-46dc-a32d-fbb9968e5938",
"expires_in": 7199,
"scope": "app"
}
check Token
var settings = {
"url": "http://localhost:8080/security/oauth/check_token?token=c0955d7f-23fb-4ca3-8a52-c715867cbef2",
"method": "GET",
"timeout": 0,
};
$.ajax(settings).done(function (response) {
console.log(response);
});
// 返回
{
"aud": [
"resourceId"
],
"exp": 1618241690,
"user_name": "gang",
"client_id": "client",
"scope": [
"app"
]
}
TokenStore TokenStore 是一个接口 , 既然是一个接口 , 就意味着使用中是可以完全定制的
public interface TokenStore {
// 通过 OAuth2AccessToken 对象获取一个 OAuth2Authentication
OAuth2Authentication readAuthentication(OAuth2AccessToken token);
OAuth2Authentication readAuthentication(String token);
// 持久化关联 OAuth2AccessToken 和 OAuth2Authentication
void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
// OAuth2AccessToken 的获取和移除
OAuth2AccessToken readAccessToken(String tokenValue);
void removeAccessToken(OAuth2AccessToken token);
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
// OAuth2RefreshToken 的直接操作
void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication);
OAuth2RefreshToken readRefreshToken(String tokenValue);
OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token);
void removeRefreshToken(OAuth2RefreshToken token);
// 使用刷新令牌删除访问令牌 , 该方法会被用于控制令牌数量
void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken);
// Client ID 查询令牌
Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName);
Collection<OAuth2AccessToken> findTokensByClientId(String clientId);
}
我们再来看看 TokenStore 主要的实现类 , 默认提供了 以下几种实现 :
InMemoryTokenStore
// 整体其实没什么看的 , 唯一有点特殊的就是 , 里面不是一个集合 , 而是每个业务一个集合 , 这其实相当于分库分表的处理思路
C- InMemoryTokenStore
- private final ConcurrentHashMap<String, OAuth2AccessToken> accessTokenStore
- private final ConcurrentHashMap<String, OAuth2AccessToken> authenticationToAccessTokenStore
- private final ConcurrentHashMap<String, Collection<OAuth2AccessToken>> userNameToAccessTokenStore
// 内部类 TokenExpiry
PSC- TokenExpiry implements Delayed
?- Delayed 是延迟处理的接口 , 用于判断 Token 是否过期
- private final long expiry;
- private final String value;
JdbcTokenStore
JdbcTokenStore 是可行的处理方式 , 但是并不是最优解 , 数据库处理对高并发 , 高性能会带来不小的挑战
// 关键点一 : SQL 写死了 , 点开就能看到 , sql 是定死的 , 但是提供了 Set 方法 , 即可定制
private static final String DEFAULT_ACCESS_TOKEN_INSERT_STATEMENT = "insert into oauth_access_token (toke....."
private String insertAccessTokenSql = DEFAULT_ACCESS_TOKEN_INSERT_STATEMENT;
// 关键点二 : 使用 JDBCTemplate , 意味着常规Spring 配置即可
private final JdbcTemplate jdbcTemplate;
RedisTokenStore
Redis 存储 Token , 比较常见的存储方式 , 一般是首选方案 , 环境影响不能使用才会次选 JDBC
后面说一下它的另外2个特别的实现类 , 他们不是一种持久化的方式
JwkTokenStore
private final TokenStore delegate : 通过该对象再去处理底层的方式
// 常见的构造器
public JwkTokenStore(String jwkSetUrl)
public JwkTokenStore(List<String> jwkSetUrls)
public JwkTokenStore(String jwkSetUrl, AccessTokenConverter accessTokenConverter)
public JwkTokenStore(String jwkSetUrl, JwtClaimsSetVerifier jwtClaimsSetVerifier)
public JwkTokenStore(String jwkSetUrl, AccessTokenConverter accessTokenConverter,JwtClaimsSetVerifier jwtClaimsSetVerifier)
public JwkTokenStore(List<String> jwkSetUrls, AccessTokenConverter accessTokenConverter,JwtClaimsSetVerifier jwtClaimsSetVerifier)
扩展资料 :
JwtTokenStore
这个对象其实是一个全新的体系 , 是 Token 的 JWT 实现 , 而不仅仅只是一种存储方式
- private JwtAccessTokenConverter jwtTokenEnhancer;
- private ApprovalStore approvalStore;
- JdbcApprovalStore
- TokenApprovalStore
- InMemoryApprovalStore
事件用于推送 , 主要使用的有 DefaultAuthenticationEventPublisher ,我们来看看他
DefaultAuthenticationEventPublisher
从构造器里面可以看到大概的事件类型
public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
addMapping(BadCredentialsException.class.getName(),AuthenticationFailureBadCredentialsEvent.class);
addMapping(UsernameNotFoundException.class.getName(),AuthenticationFailureBadCredentialsEvent.class);
addMapping(AccountExpiredException.class.getName(),AuthenticationFailureExpiredEvent.class);
addMapping(ProviderNotFoundException.class.getName(),AuthenticationFailureProviderNotFoundEvent.class);
addMapping(DisabledException.class.getName(),AuthenticationFailureDisabledEvent.class);
addMapping(LockedException.class.getName(),AuthenticationFailureLockedEvent.class);
addMapping(AuthenticationServiceException.class.getName(),AuthenticationFailureServiceExceptionEvent.class);
addMapping(CredentialsExpiredException.class.getName(),AuthenticationFailureCredentialsExpiredEvent.class);
addMapping( "org.springframework.security.authentication.cas.ProxyUntrustedException",
AuthenticationFailureProxyUntrustedEvent.class);
}
M- publishAuthenticationSuccess
?- 发布认证成功事件
M- publishAuthenticationFailure
- AbstractAuthenticationEvent event = constructor.newInstance(authentication, exception);
?- 构建一个 AbstractAuthenticationEvent
- applicationEventPublisher.publishEvent(event)
?- 发布事件
M- setAdditionalExceptionMappings
?- 将额外的异常设置为事件映射。它们会自动与ProviderManager定义的事件映射的默认异常合并
ResourceServerTokenServices 接口
public interface ResourceServerTokenServices {
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;
OAuth2AccessToken readAccessToken(String accessToken);
}
DefaultTokenServices 默认的 Token 处理类 , 没看到特别的东西, 主要是对 TokenStore 的调用
RemoteTokenServices
C- RemoteTokenServices
F- private RestOperations restTemplate;
- 简单点说就是通过这个对象调用 check_token 接口查询 token 信息
- 注意 , 区别于本地类 , 这种方式目的应该是当前 OAuth 服务作为一个 SP 的情况
TokenGranter 是一个接口 , 他有很多实现类,
其中最常见的应该是 AuthorizationCodeTokenGranter 和 ImplicitTokenGranter , RefreshTokenGranter
C- AuthorizationCodeTokenGranter
M- getOAuth2Authentication
- Paramters 中获取 Code , 并且判空 -> InvalidRequestException
- authorizationCodeServices.consumeAuthorizationCode(authorizationCode) : 通过 Code 获取 OAuth2Authentication
- 判断 redirectUri 和 clientId 是否存在 -> RedirectMismatchException/InvalidClientException
C- DefaultAccessTokenConverter
?- 默认 Token 处理体系 ,我们来看一下主要做了什么
M- convertAccessToken
?- 可以看到 , 整个转换逻辑中会通过不同的开关 , 决定显示哪些
OAuth 中除了原本的 User 概念 ,同时还有个 Client 概念 ,每个 Client 都可以看成一类待认证的对象 , **Spring OAuth 中提供了 OAuth 协议的自动配置 **, 主要包含2个类 :
// ResourceServerConfiguration
F- private TokenStore tokenStore; // token 管理实现
F- private AuthenticationEventPublisher eventPublisher; // 事件发布
F- private Map<String, ResourceServerTokenServices> tokenServices; // Token Service 集合
F- private ApplicationContext context;
F- private List<ResourceServerConfigurer> configurers = Collections.emptyList();
?- 这里的集合可以用于自己定制 ResourceServerConfigurer 类
F- private AuthorizationServerEndpointsConfiguration endpoints;
?- 对 EndPoint 接口做一个初始化操作
PSC- NotOAuthRequestMatcher
M- configure(HttpSecurity http)
?- 核心配置方法 , 主要生成了一个 ResourceServerSecurityConfigurer 放在 HttpSecurity 中
?- 这里实际上是克隆了一个当前对象给 HttpSecurity ,而不是一个引用
- 前面几步分别是 : 配置 tokenServices + tokenStore + eventPublisher
- 然后发现一个有意思的地方 : 从结构上讲 , 这应该算是装饰器的应用
for (ResourceServerConfigurer configurer : configurers) {
configurer.configure(resources);
}
- 后面几步开始对 HttpSecurity 本身做配置 , 分别是
- authenticationProvider : AnonymousAuthenticationProvider
- exceptionHandling
- accessDeniedHandler
- sessionManagement : session 管理
- sessionCreationPolicy
- 跨域处理 csrf
- 添加 requestMatcher
- 然后又发现了一个有趣的地方 , 双方互相持有对象
for (ResourceServerConfigurer configurer : configurers) {
configurer.configure(http);
}
// AuthorizationServerSecurityConfiguration
protected void configure(HttpSecurity http) throws Exception {
// 看样子和上面一样 , 构建一个新得 AuthorizationServerSecurityConfigurer 放入 HttpSecurity 中
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
//
FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
// 装饰器持有对象 , 获得更多的扩展功能
configure(configurer);
http.apply(configurer);
// 此处就是获取 OAuth 的相关接口 , 并且在下面为其配置对应的权限要求
String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
}
// 略.... 没什么关键的 , 都是通用的东西
http
.authorizeRequests()
.antMatchers(tokenEndpointPath).fullyAuthenticated()
.antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
.antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
.and()
.requestMatchers()
.antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}
看完了配置逻辑 , 我们来看一下主要的运行逻辑 :
我们从请求的 入口 authorize 来开始 , 看看请求经过了哪些途径 , 来到了这里
流程开始
跳过一系列 Invoke 和 MVC 的流程 , 找到了 FilterChainProxy 类 , 大概就能知道 , OAuth 协议是在Filter 整体流程里面对请求进行的过滤
下面来找一下是哪个过滤类讲请求拦截到登录页的 :
Step 1 : SecurityContextPersistenceFilter
这里主要是执行 Filter 链后在 finally 中处理
Step 2 : BasicAuthenticationFilter
可以看到 , 代码中做了这些事情
C01- BasicAuthenticationFilter
String header = request.getHeader("Authorization");
?- Basic Z2FuZzoxMjM0NTY=
// 如果 header 为空 , 则继续执行 Filter
if (header == null || !header.toLowerCase().startsWith("basic ")) {
chain.doFilter(request, response);
return;
}
// 如果认证信息存在
- String[] tokens = extractAndDecodeHeader(header, request);
- String username = tokens[0];
- new UsernamePasswordAuthenticationToken(username, tokens[1]);
?- 构建一个 UsernamePasswordAuthenticationToken
- .... (PS : 这里的逻辑 Filter 详细说过了 , 就不反复说了)
- SecurityContextHolder.getContext().setAuthentication(authResult);
Filter 的逻辑其实之前就已经讲了 , 这里也就不太深入了
其他扩展 Filter
TokenEndpointAuthenticationFilter
TokenEndpoint的可选身份验证过滤器。它位于客户端的另一个过滤器(通常是BasicAuthenticationFilter)的下游,如果请求也包含用户凭证,它就会为Spring SecurityContext创建一个OAuth2Authentication .
如果使用这个过滤器,Spring安全上下文将包含一个OAuth2Authentication封装(作为授权请求)、进入过滤器的表单参数和来自已经经过身份验证的客户端身份验证的客户端id,以及从请求中提取并使用身份验证管理器验证的已验证用户令牌。
OAuth2AuthenticationProcessingFilter
针对OAuth2受保护资源的认证前过滤器。
从传入请求提取一个OAuth2令牌,并使用它用OAuth2Authentication(如果与OAuth2AuthenticationManager一起使用)填充Spring安全上下文。
注意 , 到这个接口时候 ,认证其实已经完成了 , 拦截的过程详见上文 Filter , 这一部分只分析内部的流程
接口一 : authorize
http://localhost:8080/security/oauth/authorize
C06- AuthorizationEndpoint
M601- authorize
P- Map<String, Object> model
P- Map<String, String> parameters : 传入的参数
P- SessionStatus sessionStatus
P- Principal principal : 因为实际上已经认证完了 , 所以能拿到 Principal
- getOAuth2RequestFactory().createAuthorizationRequest(parameters);
?- 通过 parameters 生成了一个 AuthorizationRequest , 该对象为认证过程中的流转对象
- authorizationRequest.getResponseTypes() : 获取 tResponseTypes 的Set<String>
?- 如果集合类型正确 -> UnsupportedResponseTypeException
?- TODO : 为什么是集合 ?
- authorizationRequest.getClientId() : 校验 ClientId 是否存在 -> InvalidClientException
- principal.isAuthenticated() : 校验是否认证 -> InsufficientAuthenticationException
- authorizationRequest.setRedirectUri(resolvedRedirect) : 生成并且设置重定向地址
?- 注意 , 这个地址此时还不带 Code
- oauth2RequestValidator.validateScope(authorizationRequest, client)
?- 校验当前 client 的作用域是否包含当前请求
- userApprovalHandler.checkForPreApproval(authorizationRequest,(Authentication) principal)
?- TODO
- userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal)
?- 请求是否已被最终用户(或其他流程)批准
- getAuthorizationCodeResponse(authorizationRequest,(Authentication) principal)
?- ResponseType = code 时的最终处理逻辑 :M602
?- ResponseType = token 时的最终处理逻辑 :M605
M602- getAuthorizationCodeResponse
- getSuccessfulRedirect(authorizationRequest,generateCode(authorizationRequest, authUser)):M603
M603- getSuccessfulRedirect
- Map<String, String> query = new LinkedHashMap<String, String>();
- query.put("code", authorizationCode);
?- 插入 Code
- String state = authorizationRequest.getState();
- if (state != null) query.put("state", state);
?- 插入 State
M604- generateCode
- OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
- OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);
- String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);
?- 核心方法 , 注意 这里的 AuthorizationCodeServices 是一个接口 , 意味着该对象是可以自定义实现的
?- 这里的生成类是 RandomValueStringGenerator
// 补充 Token 模式
当使用 Implicit 模式进行认证的时候 , 这里是怎么处理的呢 ?
M605- getImplicitGrantResponse(authorizationRequest)
- TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(authorizationRequest, "implicit");
- OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);
- OAuth2AccessToken accessToken = getAccessTokenForImplicitGrant(tokenRequest, storedOAuth2Request);
?- 此方法中生成最后的 Token , 如果为空会抛出异常
- getTokenGranter().grant("implicit",new ImplicitTokenRequest(tokenRequest, storedOAuth2Request));
接口二 : AccessToken 接口 /oauth/token
C07- TokenEndpoint
?- Token 的处理主要集中在该类中 , 该类中提供了 POST 和 GET 两种请求能力 , 这2种无明显区别
M701- postAccessToken(Principal principal,Map<String, String> parameters)
- 判断是否已经认证
- getClientDetailsService().loadClientByClientId(clientId)
?- 先获取 clientId , 再获取一个 ClientDetails
- getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient)
?- 构建出一个 TokenRequest
- 校验 Client ID , 再校验 ClientDetails 的 Scope 域
- 校验 GrantType 是否合理 , 不能为 空 , 不能为 implicit
- tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
?-对 RefreshToken 类型 进行处理
- getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest)
?- 核心 , 生成 AccessToken , 详见上文 CodeToken 生成逻辑
- getResponse(token) : 生成一个 Response 对象
接口三 : CheckTokenEndpoint - /oauth/check_token
C08- CheckTokenEndpoint
M801- checkToken
- OAuth2AccessToken token = resourceServerTokenServices.readAccessToken(value);
?- 通过 Token 获取 OAuth2AccessToken 对象
- OAuth2Authentication authentication = resourceServerTokenServices.loadAuthentication(token.getValue());
?- 如果 OAuth2AccessToken 存在且没过期 , 获取 OAuth2Authentication
- accessTokenConverter.convertAccessToken(token, authentication)
?- 返回用户信息
Client 核心处理
Client 也是 OAuth 中一个非常核心的概念 , 毫无意外 , Client 的校验仍然是通过 Filter 处理的
C- ClientCredentialsTokenEndpointFilter
M- attemptAuthentication
- String clientId = request.getParameter("client_id");
- String clientSecret = request.getParameter("client_secret");
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
?- 如果认证过了 , 则直接返回 (PS : 这里是 Client 认证)
- UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,clientSecret);
?- 构建一个 UsernamePasswordAuthenticationToken , 用于认证 Client
- his.getAuthenticationManager().authenticate(authRequest)
// 后面会调用 ProviderManager 用于认证处理
C- ProviderManager
M- authenticate
FOR - getProviders()
- result = provider.authenticate(authentication) : 完成认证
// DaoAuthenticationProvider
可以看到 , 这里完全是将 Client 当成一个用户在认证 , 整个体系都得到了通用
业务处理之 : OAuth2ErrorHandler 异常处理
TODO
AuthenticationProvider 体系结构
AuthenticationProvider 的整个体系结构异常庞大 , 他是认证的主体
TODO
DefaultStateKeyGenerator
默认state 构建工具
private RandomValueStringGenerator generator = new RandomValueStringGenerator();
?- 使用的是随机数
OAuth2RestTemplate
OAuth2 的定制 RestTemplate 使用所提供资源的凭据发出oauth2认证的Rest请求
// 其中包含了一些和 OAuth 相关的定制
- appendQueryParameter : 构建token 请求的 parameter
- acquireAccessToken : 构建一个 OAuth2AccessToken ??
- getAccessToken : 必要情况下获取或更新当前上下文的访问令牌
- createRequest : 创建一个请求 , 会调用 DefaultOAuth2RequestAuthenticator 生成一个 Token
C- DefaultOAuth2RequestAuthenticator
?- 通过 AccessToken 生成一个 OAuth2Request
- Authorization Bearer ....
ProviderDiscoveryClient
看这代码 , OAuth2 应该还支持 OIDC 呢 , 该类用于发现 OIDC 规范配置的提供者的客户端
public ProviderConfiguration discover() {
// 发起请求
Map responseAttributes = this.restTemplate.getForObject(this.providerLocation, Map.class);
ProviderConfiguration.Builder builder = new ProviderConfiguration.Builder();
// 获取 OIDC 信息
builder.issuer((String)responseAttributes.get(ISSUER_ATTR_NAME));
builder.authorizationEndpoint((String)responseAttributes.get(AUTHORIZATION_ENDPOINT_ATTR_NAME));
if (responseAttributes.containsKey(TOKEN_ENDPOINT_ATTR_NAME)) {
builder.tokenEndpoint((String)responseAttributes.get(TOKEN_ENDPOINT_ATTR_NAME));
}
if (responseAttributes.containsKey(USERINFO_ENDPOINT_ATTR_NAME)) {
builder.userInfoEndpoint((String)responseAttributes.get(USERINFO_ENDPOINT_ATTR_NAME));
}
if (responseAttributes.containsKey(JWK_SET_URI_ATTR_NAME)) {
builder.jwkSetUri((String)responseAttributes.get(JWK_SET_URI_ATTR_NAME));
}
return builder.build();
}
// 基本上能看到这些 OIDC 的属性
private static final String PROVIDER_END_PATH = "/.well-known/openid-configuration";
private static final String ISSUER_ATTR_NAME = "issuer";
private static final String AUTHORIZATION_ENDPOINT_ATTR_NAME = "authorization_endpoint";
private static final String TOKEN_ENDPOINT_ATTR_NAME = "token_endpoint";
private static final String USERINFO_ENDPOINT_ATTR_NAME = "userinfo_endpoint";
private static final String JWK_SET_URI_ATTR_NAME = "jwks_uri";
我们最终的目的是为了知道哪些节点可以扩展 :
Security 可以扩展的地方主要有这几类 :
ResourceServerTokenServices
ClientDetailsService
AuthorizationServerConfigurer
OAuth2AuthenticationFailureEvent
OAuth2ClientAuthenticationProcessingFilter
TODO : 这个光说没用 , 后续会尝试做个 Demo 出来
阅读量:2014
点赞量:0
收藏量:0