Spring Security OAuth2源码解析(一)

目录

 

引入

AuthorizationServerEndpointsConfiguration

属性

AuthorizationEndpoint

 OAuth2RequestFactory

DefaultOAuth2RequestFactory 

 TokenEndpoint

TokenGranter 

AuthorizationServerTokenServices

扫描二维码关注公众号,回复: 13128678 查看本文章

DefaultTokenServices

TokenStore

AuthorizationServerSecurityConfiguration

HttpSecurity 

ClientDetailsServiceConfiguration 

AuthorizationServerEndpointsConfiguration


引入

在引入OAuth2时,会在@Configuration注解的bean上添加@EnableAuthorizationServer注解。

@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
@Deprecated
public @interface EnableAuthorizationServer {

}

@EnableAuthorizationServer注解主要引入了2个类:AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration,分别用于配置授权端点和鉴权配置。

AuthorizationServerEndpointsConfiguration

属性

定义了端点配置,鉴权服务器器配置,以及client 信息服务。

	private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();
	@Autowired
	private ClientDetailsService clientDetailsService;
	@Autowired
	private 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 从 参数中获取信息创建 AuthorizationRequest 授权请求对象
		AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

		Set<String> responseTypes = authorizationRequest.getResponseTypes();
        //参数中需要包含token或者code。
		if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
			throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
		}
        //必须clientId
		if (authorizationRequest.getClientId() == null) {
			throw new InvalidClientException("A client id must be provided");
		}

		try {
            // 判断  principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,
            //如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,
            //最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因
 	
			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());

            //获取参数中的回调地址并且与系统配置的回调地址对比。用于判断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);


            //SCOPE
			oauth2RequestValidator.validateScope(authorizationRequest, client);

		 
            //检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 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()) {
                //隐式授权方式。
				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;
		}

	}

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参数
		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);
            //验证结果
			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();
			}
            //授权码模式
			return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
		}
		finally {
			sessionStatus.setComplete();
		}

	}

 OAuth2RequestFactory

OAuth2RequestFactory用于构造鉴权请求。

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 

DefaultOAuth2RequestFactoryOAuth2RequestFactory的默认实现。从参数中获取client_id,state,scope,redirect_uri,response_type,user_oauth_approval,grant_type的值,构造请求,并通过clientDetailsService获取client的ClientDetails。

 TokenEndpoint

	@Bean
	public 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 {
        // 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
		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有默认scopes,忽略fatory添加的。
			tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
		}
        // 调用 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);
}

 主要对应4种实现方式,再加上刷新token。

 标准逻辑:AbstractTokenGranter实现

public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        //授权方式一致判断。
		if (!this.grantType.equals(grantType)) {
			return null;
		}
		
		String clientId = tokenRequest.getClientId();
		ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        //验证client的授权方式
		validateGrantType(grantType, client);

		if (logger.isDebugEnabled()) {
			logger.debug("Getting access token for: " + clientId);
		}

        //获取token
		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);
	}    

 AuthorizationCodeTokenGranter

AuthorizationCodeTokenGranter是最复杂的。

@Override
	protected 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
		.........省略
        
        // 创建一个全新的 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();
		// 创建一个全新的 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的默认实现类。

@Transactional
 public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
     // 1、 通过 tokenStore 获取到之前存在的token 并判断是否为空、过期,不为空且未过期则直接返回原有存在的token (由于我们常用Jwt 所以这里是 JwtTokenStore ,且 existingAccessToken 永远为空,即每次请求获取token的值均不同,这与RedisTokenStore 是有区别的)
     
 	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、 调用 createRefreshToken 方法生成 refreshToken
 	if (refreshToken == null) {
 		refreshToken = createRefreshToken(authentication);
 	}else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
 		ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
 		if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
 			refreshToken = createRefreshToken(authentication);
 		}
 	}
     
     // 3、 调用  createAccessToken(authentication, refreshToken) 方法获取 token
 	OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
 	tokenStore.storeAccessToken(accessToken, authentication);
 	// 4、 重新覆盖原有的刷新token(原有的 refreshToken 为UUID 数据,覆盖为 jwtToken)
 	refreshToken = accessToken.getRefreshToken();
 	if (refreshToken != null) {
 		tokenStore.storeRefreshToken(refreshToken, authentication);
 	}
 	return accessToken;

 }
 

 createAccessToken

真正产生token的地方,默认token为uuid。如果需要增加可以使用TokenEnhancerTokenEnhancerChain实现类 )。

private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
     // 1、 通过 UUID 创建  DefaultOAuth2AccessToken  并设置上有效时长等信息
 	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增强器 accessTokenEnhancer ,存在则调用增强器增强方法
 	return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
 }

 配置增强器

     /**
      * 自定义token扩展链
      *
      * @return tokenEnhancerChain
      */
     @Bean
     public TokenEnhancerChain tokenEnhancerChain() {
         TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
         tokenEnhancerChain.setTokenEnhancers(Arrays.asList(new JwtTokenEnhance(), jwtAccessTokenConverter()));
         return tokenEnhancerChain;
     }

TokenStore

 token存储。提供读取,写入,删除token等操作。

AuthorizationServerSecurityConfiguration

此类继承WebSecurityConfigurerAdapter,用于配置Security。并且order设置为0,最先配置。

@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
@Deprecated
@Order(0)
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Autowired
	private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

	@Autowired
	private ClientDetailsService clientDetailsService;

	@Autowired
	private AuthorizationServerEndpointsConfiguration endpoints;

引入ClientDetailsServiceConfigurationAuthorizationServerEndpointsConfiguration

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:off
		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);
		// @formatter:on
		http.setSharedObject(ClientDetailsService.class, clientDetailsService);
	}

ClientDetailsServiceConfiguration 

用于配置ClientDetailsService Bean。

public class ClientDetailsServiceConfiguration {

	@SuppressWarnings("rawtypes")
	private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
	
	@Bean
	public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {
		return configurer;
	}

	@Bean
	@Lazy
	@Scope(proxyMode=ScopedProxyMode.INTERFACES)
	public ClientDetailsService clientDetailsService() throws Exception {
		return configurer.and().build();
	}

ClientDetailsService 定义一个方法loadClientByClientId,通过clientId获取Client信息。有2个具体实现:InMemoryClientDetailsServiceJdbcClientDetailsService

public interface ClientDetailsService {

  ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException;

}
public interface ClientDetails extends Serializable {
	String getClientId();

	//可以访问的资源列表
	Set<String> getResourceIds();
    //是否需要秘钥
	boolean isSecretRequired();
	//秘钥
	String getClientSecret();

	//是否有权限范围
	boolean isScoped();

	//权限范围
	Set<String> getScope();

	//授权类型
	Set<String> getAuthorizedGrantTypes();

	//返回uri,可以用于判断client
	Set<String> getRegisteredRedirectUri();

	Collection<GrantedAuthority> getAuthorities();

	Integer getAccessTokenValiditySeconds();
	Integer getRefreshTokenValiditySeconds();
	boolean isAutoApprove(String scope);
    //附加信息
	Map<String, Object> getAdditionalInformation();

}

AuthorizationServerEndpointsConfiguration

见上面。

猜你喜欢

转载自blog.csdn.net/demon7552003/article/details/107823396