关于SpringOauth2理解
OAuth2.0说明
官网学习地址:
角色
在 OAuth2.0 中,有如下角色:
① Authorization Server:认证服务器,用于认证用户。如果客户端认证通过,则发放访问资源服务器的令牌。
② Resource Server:资源服务器,拥有受保护资源。如果请求包含正确的访问令牌,则可以访问资源。
友情提示:提供管理后台、客户端 API 的服务,都可以认为是 Resource Server。
③ Client:客户端。它请求资源服务器时,会带上访问令牌,从而成功访问资源。
友情提示:Client 可以是浏览器、客户端,也可以是内部服务。
④ Resource Owner:资源拥有者。最终用户,他有访问资源的账号与密码。
友情提示:可以简单把 Resource Owner 理解成人,她在使用 Client 访问资源。
认证流程
-
(A)用户打开客户端以后,客户端要求用户给予授权。
-
(B)用户同意给予客户端授权。
-
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
-
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
-
(E)客户端使用令牌,向资源服务器申请获取资源。
-
(F)资源服务器确认令牌无误,同意向客户端开放资源。
源码分析
启动流程
@EnableAuthorizationServer` 此注解可以了解到Springboot enable模式,关于bean注册的SPI原理可以有空看下(个人认为在springboot原理中还是比较重要的,包括自动注入,和enable)
·用于启用授权服务器,在当前应用程序上下文中,它必须是 DispatcherServlet上下文。很多可以使用{authorizationserverconfigurer}类型的@Beans
来定制服务器的特性(例如,通过扩展{@link AuthorizationServerConfigurerAdapter}。用户负责保护使用普通Spring安全特性( EnableWebSecurity)的授权端点(/oauth/authorizeEnableWebSecurity}等,但是令牌端点(/oauth/Token)将使用httpbasic自动保护对客户端凭据进行身份验证。必须通过提供{ ClientDetailsService}通过一个或多个授权服务器配置程序。
@Import()
注解在Spring Boot使用较多,相对比的xml引入另一个xml(可以学习下)
我们可以看到其源码内部导入了 AuthorizationServerEndpointsConfiguration
和 AuthorizationServerSecurityConfiguration
这2个配置类。 接下来我们分别看下这2个配置类具体做了什么。
@EnableAuthorizationServer └── AuthorizationServerEndpointsConfiguration └── TokenKeyEndpointRegistrar ├── AuthorizationServerSecurityConfiguration └── ClientDetailsServiceConfiguration └── AuthorizationServerEndpointsConfiguration
一. AuthorizationServerEndpointsConfiguration
提供端点的访问配置对象
1.authorizationEndpoint()
用于服务于授权请求。预设地址:/oauth/authorize
2.TokenEndpoint()
用于服务访问令牌的请求。预设地址:/oauth/token
3.checkTokenEndpoint()
用于服务访问令牌的校验。预设地址:/oauth/check_token
TokenKeyEndpoint.registry.registerBeanDefinition();
注册bean
二.AuthorizationServerSecurityConfiguration
(实际这个加载bean的时候要优先AuthorizationServerEndpointsConfiguration
)可以看order排序注解
1.@import ClientDetailsServiceConfiguration
构建configurers对象
@Configuration public class ClientDetailsServiceConfiguration { @SuppressWarnings("rawtypes") private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder()); @Bean public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() { return configurer; } }
设置clientDetails
属性
@Autowired public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception { for (AuthorizationServerConfigurer configurer : configurers) { //继承父类AuthorizationServerConfigurer,但是它下面有空实现,我们来继承子类覆盖就可以 //AuthorizationServerConfigurerAdapter configurer.configure(clientDetails); } }
2.AuthorizationServerEndpointsConfiguration
@PostConstruct public void init() { for (AuthorizationServerConfigurer configurer : configurers) { try { //设置端点权限配置,我们可以集成重写 configurer.configure(endpoints); //继承父类AuthorizationServerConfigurer,但是它下面有空实现,我们来继承子类覆盖就可以 //AuthorizationServerConfigurerAdapter } catch (Exception e) { throw new IllegalStateException("Cannot configure enpdoints", e); } } endpoints.setClientDetailsService(clientDetailsService); }
AuthorizationServerConfigurer的子类
最主要的配置 ClientDetailsService()
、 ClientDetailsService()
以及 ClientCredentialsTokenEndpointFilter()
-
ClientDetailsService : 内部仅有 loadClientByClientId 方法。从方法名我们就可知其是通过 clientId 来获取 Client 信息, 官方提供 JdbcClientDetailsService、InMemoryClientDetailsService 2个实现类,我们也可以像UserDetailsService 一样编写自己的实现类。
-
UserDetailsService : 内部仅有 loadUserByUsername 方法。这个类不用我再介绍了吧。不清楚得同学可以看下我之前得文章。
-
ClientDetailsUserDetailsService : UserDetailsService子类,内部维护了 ClientDetailsService 。其 loadUserByUsername 方法重写后调用ClientDetailsService.loadClientByClientId()。
访问流程
-
用户名和密码模式
请求curl
curl 'http://127.0.0.1:8080/auth/oauth/token?grant_type=password&username=admin&password=123456&scope=server&client_id=xxx&client_secret=xxxx'
首先debug到
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken()
这个类的方法上,删除验证的业务逻辑//获取参数中的clientId String clientId = getClientId(principal); // 根据clientId拿去客户端详细信息(可以实现集成ClientDetailsService下面的子类) ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); //可以看下面代码。实际上是把请求参数和authenticatedClient信息做了个融合 //TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType); //跳过一些验证可以看到最终构建的是一个tokenRequest对象也就是流程图的地4步 TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); //生产token的地方 //getTokenGranter获取grant_type=password对应的接口,通过CompositeTokenGranter获取合适的 //TokenGranter,这里调用的抽象类里面的父类 // OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); return getResponse(token);
//可以看下面代码。实际上是把请求参数和authenticatedClient信息做了个融合 //TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType); public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) { String clientId = requestParameters.get(OAuth2Utils.CLIENT_ID); String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE); Set<String> scopes = extractScopes(requestParameters, clientId); TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType); return tokenRequest; }
getTokenGranter()获取对应的grant_type=password找到对应的处理密码模式使用的 ResourceOwnerPasswordTokenGranter.class
//包含校验类型下面的4中类型的验证,以及刷新令牌的 TokenGranter private final List<TokenGranter> tokenGranters; //在上面tokenGranters选中一个生成token public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { for (TokenGranter granter : tokenGranters) { OAuth2AccessToken grant = granter.grant(grantType, tokenRequest); if (grant!=null) { return grant; } } return null; }
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) { if (!this.grantType.equals(grantType)) { return null; } String clientId = tokenRequest.getClientId(); ClientDetails client = clientDetailsService.loadClientByClientId(clientId); validateGrantType(grantType, client); if (logger.isDebugEnabled()) { logger.debug("Getting access token for: " + clientId); } //核心方法,获取accesstoken return getAccessToken(client, tokenRequest); } //核心方法,获取accesstoken protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { //getOAuth2Authentication要获取token需要一个OAuth2Authentication对象 //最后看createAccessToken方法 //getOAuth2Authentication这个方法有不同的实现,每个登陆方式不同,处理认证的逻辑不一样 // 创建accesstoken需要2个对象,一个是tokenreQuest,另一个是我服务器认证的信息 //本类是个抽象类,可以看子类方法实现 密码登录使用的是 //ResourceOwnerPasswordTokenGranter.getOAuth2Authentication(); //最后创建token return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)); }
@Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters()); String username = parameters.get("username"); String password = parameters.get("password"); // Protect from downstream leaks of password parameters.remove("password"); //生成Authentication这个对象 交给authenticationManager去验证。 Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); //authenticationManager会去调用实现UserDetailsService userAuth = authenticationManager.authenticate(userAuth); //这里没有什么东西,就new OAuth2Request OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); //返回OAuth2Authentication认证信息 return new OAuth2Authentication(storedOAuth2Request, userAuth); }
@Transactional public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { //首先gettoken OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication); OAuth2RefreshToken refreshToken = null; if (existingAccessToken != null) { //失效的token if (existingAccessToken.isExpired()) { //已经存在的token if (existingAccessToken.getRefreshToken() != null) { refreshToken = existingAccessToken.getRefreshToken(); tokenStore.removeRefreshToken(refreshToken); } tokenStore.removeAccessToken(existingAccessToken); } else { tokenStore.storeAccessToken(existingAccessToken, authentication); return existingAccessToken; } } //创建token tokenStore.storeAccessToken(accessToken, authentication); // In case it was modified refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; }
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString()); int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request()); if (validitySeconds > 0) { token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L))); } token.setRefreshToken(refreshToken); token.setScope(authentication.getOAuth2Request().getScope()); //设置token以及使用token增强器 return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; }