javascript
Spring Security OAuth2源码解析(一)
目錄
?
引入
AuthorizationServerEndpointsConfiguration
屬性
AuthorizationEndpoint
?OAuth2RequestFactory
DefaultOAuth2RequestFactory?
?TokenEndpoint
TokenGranter?
AuthorizationServerTokenServices
DefaultTokenServices
TokenStore
AuthorizationServerSecurityConfiguration
HttpSecurity?
ClientDetailsServiceConfiguration?
AuthorizationServerEndpointsConfiguration
引入
在引入OAuth2時,會在@Configuration注解的bean上添加@EnableAuthorizationServer注解。
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) @Deprecated public @interface EnableAuthorizationServer {}@EnableAuthorizationServer注解主要引入了2個類:AuthorizationServerEndpointsConfiguration和 AuthorizationServerSecurityConfiguration,分別用于配置授權(quán)端點(diǎn)和鑒權(quán)配置。
AuthorizationServerEndpointsConfiguration
屬性
定義了端點(diǎn)配置,鑒權(quán)服務(wù)器器配置,以及client 信息服務(wù)。
private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();@Autowiredprivate ClientDetailsService clientDetailsService;@Autowiredprivate List<AuthorizationServerConfigurer> configurers = Collections.emptyList();?
AuthorizationEndpoint
public AuthorizationEndpoint authorizationEndpoint() throws Exception {AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));authorizationEndpoint.setTokenGranter(tokenGranter());authorizationEndpoint.setClientDetailsService(clientDetailsService);authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());authorizationEndpoint.setRedirectResolver(redirectResolver());return authorizationEndpoint;}AuthorizationEndpoint??定義了
@RequestMapping(value = "/oauth/authorize")public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,SessionStatus sessionStatus, Principal principal); @RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,SessionStatus sessionStatus, Principal principal)?GET請求:
@RequestMapping(value = "/oauth/authorize")public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,SessionStatus sessionStatus, Principal principal) {// 通過 OAuth2RequestFactory 從 參數(shù)中獲取信息創(chuàng)建 AuthorizationRequest 授權(quán)請求對象AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);Set<String> responseTypes = authorizationRequest.getResponseTypes();//參數(shù)中需要包含token或者code。if (!responseTypes.contains("token") && !responseTypes.contains("code")) {throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);}//必須clientIdif (authorizationRequest.getClientId() == null) {throw new InvalidClientException("A client id must be provided");}try {// 判斷 principal 是否 已授權(quán) : /oauth/authorize 設(shè)置為無權(quán)限訪問 ,所以要判斷,//如果 判斷失敗則拋出 InsufficientAuthenticationException (AuthenticationException 子類),其異常會被 ExceptionTranslationFilter 處理 ,//最終跳轉(zhuǎn)到 登錄頁面,這也是為什么我們第一次去請求獲取 授權(quán)碼時會跳轉(zhuǎn)到登陸界面的原因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());//獲取參數(shù)中的回調(diào)地址并且與系統(tǒng)配置的回調(diào)地址對比。用于判斷client是否是合法,安全性校驗。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);//SCOPEoauth2RequestValidator.validateScope(authorizationRequest, client);//檢測該客戶端是否設(shè)置自動 授權(quán)(即 我們配置客戶端時配置的 autoApprove(true) )authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,(Authentication) principal);// TODO: is this call necessary?boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);authorizationRequest.setApproved(approved);if (authorizationRequest.isApproved()) {//隱式授權(quán)方式。if (responseTypes.contains("token")) {return getImplicitGrantResponse(authorizationRequest);}//授權(quán)碼方式。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;}}POST請求:
?
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,SessionStatus sessionStatus, Principal principal) {//必須是Spring security驗證了的if (!(principal instanceof Authentication)) {sessionStatus.setComplete();throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorizing an access token.");}//獲取authorizationRequest參數(shù)AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get(AUTHORIZATION_REQUEST_ATTR_NAME);if (authorizationRequest == null) {sessionStatus.setComplete();throw new InvalidRequestException("Cannot approve uninitialized authorization request.");}// 確保請求沒被更改@SuppressWarnings("unchecked")Map<String, Object> originalAuthorizationRequest = (Map<String, Object>) model.get(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME);if (isAuthorizationRequestModified(authorizationRequest, originalAuthorizationRequest)) {throw new InvalidRequestException("Changes were detected from the original authorization request.");}try {Set<String> responseTypes = authorizationRequest.getResponseTypes();authorizationRequest.setApprovalParameters(approvalParameters);//驗證authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,(Authentication) principal);//驗證結(jié)果boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);authorizationRequest.setApproved(approved);if (authorizationRequest.getRedirectUri() == null) {sessionStatus.setComplete();throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");}//重定向到拒絕訪問。if (!authorizationRequest.isApproved()) {RedirectView redirectView = new RedirectView(getUnsuccessfulRedirect(authorizationRequest,new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),false, true, false);redirectView.setStatusCode(HttpStatus.SEE_OTHER);return redirectView;}//隱式模式。if (responseTypes.contains("token")) {return getImplicitGrantResponse(authorizationRequest).getView();}//授權(quán)碼模式return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);}finally {sessionStatus.setComplete();}}?OAuth2RequestFactory
OAuth2RequestFactory用于構(gòu)造鑒權(quán)請求。
public interface OAuth2RequestFactory {AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters);OAuth2Request createOAuth2Request(AuthorizationRequest request);OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest);TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient);TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType); }DefaultOAuth2RequestFactory?
DefaultOAuth2RequestFactory是OAuth2RequestFactory的默認(rèn)實(shí)現(xiàn)。從參數(shù)中獲取client_id,state,scope,redirect_uri,response_type,user_oauth_approval,grant_type的值,構(gòu)造請求,并通過clientDetailsService獲取client的ClientDetails。
?
?TokenEndpoint
@Beanpublic TokenEndpoint tokenEndpoint() throws Exception {TokenEndpoint tokenEndpoint = new TokenEndpoint();tokenEndpoint.setClientDetailsService(clientDetailsService);tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());tokenEndpoint.setTokenGranter(tokenGranter());tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());return tokenEndpoint;}?TokenEndpoint定義了訪問令牌請求
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters)throws HttpRequestMethodNotSupportedException {// 驗證 用戶信息 (正常情況下會經(jīng)過 ClientCredentialsTokenEndpointFilter 過濾器認(rèn)證后獲取到用戶信息 )if (!(principal instanceof Authentication)) {throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");}String clientId = getClientId(principal);ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);// 通過客戶端信息生成 TokenRequest 對象TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);//。。。。。。。省略很多驗證。if (isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {logger.debug("Clearing scope of incoming token request");tokenRequest.setScope(Collections.<String>emptySet());} else if (isRefreshTokenRequest(parameters)) {if (StringUtils.isEmpty(parameters.get("refresh_token"))) {throw new InvalidRequestException("refresh_token parameter not provided");}//refresh token有默認(rèn)scopes,忽略fatory添加的。tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));}// 調(diào)用 TokenGranter.grant()方法生成 OAuth2AccessToken 對象(即token)OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);if (token == null) {throw new UnsupportedGrantTypeException("Unsupported grant type");}return getResponse(token);}TokenGranter?
TokenGranter用于生成token。
public interface TokenGranter {OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest); }?主要對應(yīng)4種實(shí)現(xiàn)方式,再加上刷新token。
?標(biāo)準(zhǔn)邏輯:AbstractTokenGranter實(shí)現(xiàn)
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {//授權(quán)方式一致判斷。if (!this.grantType.equals(grantType)) {return null;}String clientId = tokenRequest.getClientId();ClientDetails client = clientDetailsService.loadClientByClientId(clientId);//驗證client的授權(quán)方式validateGrantType(grantType, client);if (logger.isDebugEnabled()) {logger.debug("Getting access token for: " + clientId);}//獲取tokenreturn 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);}?AuthorizationCodeTokenGranter
AuthorizationCodeTokenGranter是最復(fù)雜的。
@Overrideprotected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {Map<String, String> parameters = tokenRequest.getRequestParameters();String authorizationCode = parameters.get("code");String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);if (authorizationCode == null) {throw new InvalidRequestException("An authorization code must be supplied.");}//通過code獲取OAuth2Authentication ,OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);if (storedAuth == null) {throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);}//驗證clientId和redirect_uri.........省略// 創(chuàng)建一個全新的 OAuth2Request,并從OAuth2Authentication 中獲取到 Authentication 對象Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request.getRequestParameters());combinedParameters.putAll(parameters);OAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);Authentication userAuth = storedAuth.getUserAuthentication();// 創(chuàng)建一個全新的 OAuth2Authentication 對象return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);}CompositeTokenGranter?
組合多個Granter,返回第一個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;}AuthorizationServerTokenServices
AuthorizationServerTokenServices,token生成與刷新。
public interface AuthorizationServerTokenServices {OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)throws AuthenticationException;OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);}DefaultTokenServices
AuthorizationServerTokenServices的默認(rèn)實(shí)現(xiàn)類。
@Transactionalpublic OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {// 1、 通過 tokenStore 獲取到之前存在的token 并判斷是否為空、過期,不為空且未過期則直接返回原有存在的token (由于我們常用Jwt 所以這里是 JwtTokenStore ,且 existingAccessToken 永遠(yuǎn)為空,即每次請求獲取token的值均不同,這與RedisTokenStore 是有區(qū)別的)OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken = null;if (existingAccessToken != null) {if (existingAccessToken.isExpired()) {if (existingAccessToken.getRefreshToken() != null) {refreshToken = existingAccessToken.getRefreshToken();tokenStore.removeRefreshToken(refreshToken);}tokenStore.removeAccessToken(existingAccessToken);}else {tokenStore.storeAccessToken(existingAccessToken, authentication);return existingAccessToken;}}// 2、 調(diào)用 createRefreshToken 方法生成 refreshTokenif (refreshToken == null) {refreshToken = createRefreshToken(authentication);}else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {refreshToken = createRefreshToken(authentication);}}// 3、 調(diào)用 createAccessToken(authentication, refreshToken) 方法獲取 tokenOAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);// 4、 重新覆蓋原有的刷新token(原有的 refreshToken 為UUID 數(shù)據(jù),覆蓋為 jwtToken)refreshToken = accessToken.getRefreshToken();if (refreshToken != null) {tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}?createAccessToken
真正產(chǎn)生token的地方,默認(rèn)token為uuid。如果需要增加可以使用TokenEnhancer(TokenEnhancerChain實(shí)現(xiàn)類 )。
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {// 1、 通過 UUID 創(chuàng)建 DefaultOAuth2AccessToken 并設(shè)置上有效時長等信息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());// 2、 判斷 是否存在 token增強(qiáng)器 accessTokenEnhancer ,存在則調(diào)用增強(qiáng)器增強(qiáng)方法return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;}?配置增強(qiáng)器
/*** 自定義token擴(kuò)展鏈** @return tokenEnhancerChain*/@Beanpublic TokenEnhancerChain tokenEnhancerChain() {TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new JwtTokenEnhance(), jwtAccessTokenConverter()));return tokenEnhancerChain;}TokenStore
?token存儲。提供讀取,寫入,刪除token等操作。
AuthorizationServerSecurityConfiguration
此類繼承WebSecurityConfigurerAdapter,用于配置Security。并且order設(shè)置為0,最先配置。
@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class }) @Deprecated @Order(0) public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredprivate List<AuthorizationServerConfigurer> configurers = Collections.emptyList();@Autowiredprivate ClientDetailsService clientDetailsService;@Autowiredprivate AuthorizationServerEndpointsConfiguration endpoints;引入ClientDetailsServiceConfiguration和AuthorizationServerEndpointsConfiguration
HttpSecurity?
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 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);}// @formatter:offhttp.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);// @formatter:onhttp.setSharedObject(ClientDetailsService.class, clientDetailsService);}?
ClientDetailsServiceConfiguration?
用于配置ClientDetailsService Bean。
public class ClientDetailsServiceConfiguration {@SuppressWarnings("rawtypes")private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());@Beanpublic ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {return configurer;}@Bean@Lazy@Scope(proxyMode=ScopedProxyMode.INTERFACES)public ClientDetailsService clientDetailsService() throws Exception {return configurer.and().build();}ClientDetailsService 定義一個方法loadClientByClientId,通過clientId獲取Client信息。有2個具體實(shí)現(xiàn):InMemoryClientDetailsService,JdbcClientDetailsService
public interface ClientDetailsService {ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;} public interface ClientDetails extends Serializable {String getClientId();//可以訪問的資源列表Set<String> getResourceIds();//是否需要秘鑰boolean isSecretRequired();//秘鑰String getClientSecret();//是否有權(quán)限范圍boolean isScoped();//權(quán)限范圍Set<String> getScope();//授權(quán)類型Set<String> getAuthorizedGrantTypes();//返回uri,可以用于判斷clientSet<String> getRegisteredRedirectUri();Collection<GrantedAuthority> getAuthorities();Integer getAccessTokenValiditySeconds();Integer getRefreshTokenValiditySeconds();boolean isAutoApprove(String scope);//附加信息Map<String, Object> getAdditionalInformation();}?
AuthorizationServerEndpointsConfiguration
見上面。
?
?
總結(jié)
以上是生活随笔為你收集整理的Spring Security OAuth2源码解析(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OAuth2(二)——实现
- 下一篇: Spring Security OAut