Spring Security OAuth2授权原理、流程与源码解读

文章目录

  • 前言
  • AuthorizationServerConfigurerAdapter(身份认证服务配置适配器)
    • OAuth2AuthorizationServerConfiguration(OAuth2授权服务配置)
  • EnableAuthorizationServer(开启身份认证服务)
    • AuthorizationServerEndpointsConfigurations身份认证服务站点配置类
      • AuthorizationEndpoint (授权码类型端点)
      • TokenEndpoint (Token端点)
      • CheckTokenEndpoint (检查Token端点)
  • AuthorizationServerSecurityConfigurer(授权服务安全配置)
    • ClientCredentialsTokenEndpointFilter (客户端认证Token端点过滤器)
  • ClientDetailsServiceConfigurer(客户端详情服务配置)
  • AuthorizationServerEndpointsConfigurer(授权服务端点配置)
    • TokenStore 设置
    • TokenGranter(Token授权)创建
  • TokenGranter(Token授权)
    • CompositeTokenGranter(综合Token授权)
    • AbstractTokenGranter(抽象Token授权)
      • RefreshTokenGranter(刷新Token授权)
      • AuthorizationCodeTokenGranter(授权码Token授权)
      • ResourceOwnerPasswordTokenGranter(密码Token授权)
      • ClientCredentialsTokenGranter(客户端凭证Token授权)
      • ImplicitTokenGranter(隐式Token授权)


前言

Spring Security OAuth2,认证原理与流程。

  1. 客户端认证由ClientCredentialsTokenEndpointFilter 完成客户端身份认证
  2. 用户授权由:TokenEndpointAuthorizationEndpoint 完成。
  3. Token创建、刷新、移除等。

执行流程如下图:
Spring Security OAuth2授权原理、流程与源码解读_第1张图片

AuthorizationServerConfigurerAdapter(身份认证服务配置适配器)

授权服务安全配置

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}

客户端详情配置

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}

授权服务端点配置

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}

OAuth2AuthorizationServerConfiguration(OAuth2授权服务配置)

默认OAuth2授权服务配置,可以作为参考

EnableAuthorizationServer(开启身份认证服务)

在这个注解中引入了两个配置类AuthorizationServerEndpointsConfiguration(授权服务站点配置)和AuthorizationServerSecurityConfiguration(授权服务安全配置)


@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})

AuthorizationServerEndpointsConfigurations身份认证服务站点配置类

AuthorizationEndpoint (授权码类型端点)

@Bean
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"));
	//token授权
	authorizationEndpoint.setTokenGranter(tokenGranter());
	//配置客户端详情
	authorizationEndpoint.setClientDetailsService(clientDetailsService);
	//身份认证授权码服务
	authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
	//OAuth2请求工厂
	authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
	//OAuth2请求校验
	authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
	//用户审批处理程序
	authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
	//重定向解析器
	authorizationEndpoint.setRedirectResolver(redirectResolver());
	return authorizationEndpoint;
}

TokenEndpoint (Token端点)

@Bean
public TokenEndpoint tokenEndpoint() throws Exception {
	TokenEndpoint tokenEndpoint = new TokenEndpoint();
	//配置客户端详情
	tokenEndpoint.setClientDetailsService(clientDetailsService);
	//异常处理Handler
	tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
	//token授权
	tokenEndpoint.setTokenGranter(tokenGranter());
	//OAuth2请求工厂
	tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
	//OAuth2请求校验
	tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
	//设置允许请求方法
	tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
	return tokenEndpoint;
}
  1. ClientCredentialsTokenEndpointFilter 完成客户端身份认证
  2. 从ClientDetailsService中获取客户端详情信息
  3. 通过OAuth2RequestFactory将请求参数和客户端详情转为TokenRequest
  4. 如果client不为空,且判断clientId和获取TokenRequest的clientId是否相等
  5. 获取的客户端详情信息通过OAuth2RequestValidator校验请求域
  6. 不支持implicit授权模式
  7. 判断是否授权码类型请求,是需要设置对应的请求域
  8. 判断是否刷新Token类型请求,是需要设置对应的请求域
  9. 通过TokenGranter完成授权,返回OAuth2AccessToken
@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.");
	}
	//从ClientDetailsService中根据clientId获取客户端详情信息
	String clientId = getClientId(principal);
	ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);

    //通过OAuth2RequestFactory将请求参数和客户端详情转为TokenRequest 
	TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
	//如果client不为空,且判断clientId和获取客户端详情的clientId是否相等
	if (clientId != null && !clientId.equals("")) {
		// Only validate the client details if a client authenticated during this
		// request.
		if (!clientId.equals(tokenRequest.getClientId())) {
			// double check to make sure that the client ID in the token request is the same as that in the
			// authenticated client
			throw new InvalidClientException("Given client ID does not match authenticated client");
		}
	}
	//获取的客户端详情信息通过OAuth2RequestValidator校验请求域
	if (authenticatedClient != null) {
		oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
	}
	if (!StringUtils.hasText(tokenRequest.getGrantType())) {
		throw new InvalidRequestException("Missing grant type");
	}
	// 不支持implicit授权模式
	if (tokenRequest.getGrantType().equals("implicit")) {
		throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
	}
	//是否授权码类型请求
	if (isAuthCodeRequest(parameters)) {
		// The scope was requested or determined during the authorization step
		if (!tokenRequest.getScope().isEmpty()) {
			logger.debug("Clearing scope of incoming token request");
			tokenRequest.setScope(Collections.<String> emptySet());
		}
	}
	//是否刷新Token类型请求
	if (isRefreshTokenRequest(parameters)) {
		// A refresh token has its own default scopes, so we should ignore any added by the factory here.
		tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
	}
	//通过Token授权,返回OAuth2访问Token
	OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
	if (token == null) {
		throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
	}

	return getResponse(token);

}

CheckTokenEndpoint (检查Token端点)

@Bean
public CheckTokenEndpoint checkTokenEndpoint() {
	CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
	endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
	endpoint.setExceptionTranslator(exceptionTranslator());
	return endpoint;
}

AuthorizationServerSecurityConfigurer(授权服务安全配置)

主要完成功能是安全配置
主要功能:ClientDetailsService转换为UserDetailsService并注入到AuthenticationManager

public void init(HttpSecurity http) throws Exception {

	registerDefaultAuthenticationEntryPoint(http);
	//将ClientDetailsService转换为UserDetailsService并注入到AuthenticationManager
	if (passwordEncoder != null) {
		ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
		clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
		http.getSharedObject(AuthenticationManagerBuilder.class)
					.userDetailsService(clientDetailsUserDetailsService)
					.passwordEncoder(passwordEncoder());
	}
	else {
		http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService()));
	}
	http.securityContext().securityContextRepository(new NullSecurityContextRepository()).and().csrf().disable()
				.httpBasic().realmName(realm);
	if (sslOnly) {
			http.requiresChannel().anyRequest().requiresSecure();
	}
}

在配置类中完成ClientCredentialsTokenEndpointFilter 加入到FilterChainProxy中完成客户端身份认证。

@Override
public void configure(HttpSecurity http) throws Exception {	
	// ensure this is initialized
	frameworkEndpointHandlerMapping();
	if (allowFormAuthenticationForClients) {
		clientCredentialsTokenEndpointFilter(http);
	}
	for (Filter filter : tokenEndpointAuthenticationFilters) {
		http.addFilterBefore(filter, BasicAuthenticationFilter.class);
	}
	http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
/** 
 * 完成客户端身份认证
*/
private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
	//创建过滤器,并设置匹配地址,默认/oauth/token
	ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
			frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
	// 设置身份认证管理器,由init()方法中获取的值
	clientCredentialsTokenEndpointFilter
				.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
	//OAuth2身份认证进入点
	OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
	authenticationEntryPoint.setTypeName("Form");
	authenticationEntryPoint.setRealmName(realm);
	clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
	clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
	//将过滤器加入到HttpSecurity 中
	http.addFilterBefore(clientCredentialsTokenEndpointFilter, BasicAuthenticationFilter.class);
	return clientCredentialsTokenEndpointFilter;
}

ClientCredentialsTokenEndpointFilter (客户端认证Token端点过滤器)

  1. 过滤器匹配地址默认:/oauth/token
  2. 获取请求参数中的:client_idclient_secret 的数据,转换为 UsernamePasswordAuthenticationToken
  3. 通过AuthenticationManager完成客户端身份认证

ClientDetailsServiceConfigurer(客户端详情服务配置)

AuthorizationServerEndpointsConfigurer(授权服务端点配置)

TokenStore 设置

private TokenStore tokenStore() {
	if (tokenStore == null) {
		if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
			this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
		}
		else {
			this.tokenStore = new InMemoryTokenStore();
		}
	}
	return this.tokenStore;
}

TokenGranter(Token授权)创建

  1. 获取ClientDetailsService
  2. 获取AuthorizationServerTokenServices
  3. 获取AuthorizationCodeServices
  4. 获取OAuth2RequestFactory
  5. 创建AuthorizationCodeTokenGranter并添加到tokenGranters
  6. 创建RefreshTokenGranter并添加到tokenGranters
  7. 创建ImplicitTokenGranter并添加到tokenGranters
  8. 创建ClientCredentialsTokenGranter并添加到tokenGranters
  9. 如果设置了AuthenticationManager 则创建ResourceOwnerPasswordTokenGranter并添加到tokenGranters
private List<TokenGranter> getDefaultTokenGranters() {
	ClientDetailsService clientDetails = clientDetailsService();
	AuthorizationServerTokenServices tokenServices = tokenServices();
	AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
	OAuth2RequestFactory requestFactory = requestFactory();

	List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
	tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
				requestFactory));
	tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
	ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
	tokenGranters.add(implicit);
	tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
	if (authenticationManager != null) {
		tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
					clientDetails, requestFactory));
	}
	return tokenGranters;
}

TokenGranter(Token授权)

CompositeTokenGranter(综合Token授权)

 List tokenGranters

通过代理方式、循环tokenGranters,根据对应的授权模式,找到指定的TokenGranter完成Token授权模式的选择。在执行方法如下:

OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest)

AbstractTokenGranter(抽象Token授权)

/*
 * 授权服务Token服务
 * 创建Token:OAuth2AccessToken createAccessToken(OAuth2Authentication authentication)
 * 刷新Token: OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
 * 获取Token:OAuth2AccessToken getAccessToken(OAuth2Authentication authentication)
 */
AuthorizationServerTokenServices tokenServices;
/*
 * 客户端详情服务
 * 获取客户端详情信息:ClientDetails loadClientByClientId(String clientId) 
 */
ClientDetailsService clientDetailsService;
/*
 * OAuth2请求工厂
 * 创建OAuth2请求: OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest)
 * 	
 */
OAuth2RequestFactory requestFactory;
/*
 * 授权码类型:authorization_code、password、refresh_token、implicit、client_credentials
 */
String grantType

授权

  1. 判断授权类型是否符支持对应的TokenGranter
  2. 从ClientDetailsService根据clientId获取ClientDetails
  3. 抽象类validateGrantType由每个子类具体完成验证授权类型。
  4. 将ClientDetails (客户端详情)和TokenRequest(Token请求) 通过OAuth2RequestFactory生成OAuth2Request,并创建未OAuth2Authentication。
  5. AuthorizationServerTokenServices(授权服务Token服务)类根据OAuth2Authentication创建OAuth2AccessToken 。
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);
	}
	return getAccessToken(client, tokenRequest);
}

RefreshTokenGranter(刷新Token授权)

  1. 刷新Token
  2. 从AuthorizationServerTokenServices根据refreshToken 更新OAuth2AccessToken
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
	String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
	return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}

AuthorizationCodeTokenGranter(授权码Token授权)

  AuthorizationCodeServices

ResourceOwnerPasswordTokenGranter(密码Token授权)

  /* 
   * 授权类型:password
   *  身份认证管理
   * /
  AuthenticationManager

验证授权类型

  1. 根据username和password创建UsernamePasswordAuthenticationToken
  2. 通过AuthenticationManager完成用户身份认证
  3. 如果抛出异常,则根据不同的异常给予不同的抛出异常信息。
  4. 认证通过,则创建OAuth2Authentication
@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");
	//根据username和password创建UsernamePasswordAuthenticationToken
	//不同的Authentication支持不通的AuthenticationProvider类
	//UsernamePasswordAuthenticationToken支持类有AbstractUserDetailsAuthenticationProvider类的实现
	Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
	((AbstractAuthenticationToken) userAuth).setDetails(parameters);
	try {
		//通过AuthenticationManager完成用户身份认证
		userAuth = authenticationManager.authenticate(userAuth);
	}catch (AccountStatusException ase) {
		//账户状态异常,则抛出无效授权异常
		throw new InvalidGrantException(ase.getMessage());
	}catch (BadCredentialsException e) {
		// 用户名或密码校验异常
		throw new InvalidGrantException(e.getMessage());
	}
	if (userAuth == null || !userAuth.isAuthenticated()) {
		throw new InvalidGrantException("Could not authenticate user: " + username);
	}
		
	OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
	return new OAuth2Authentication(storedOAuth2Request, userAuth);
}

ClientCredentialsTokenGranter(客户端凭证Token授权)

  1. 客户端凭证Token授权
  2. 根据父类(AbstractTokenGranter)方法执行grant(授权方法)获得OAuth2AccessToken
  3. 创建DefaultOAuth2AccessToken,并根据是否允许刷新Token
  4. 返回DefaultOAuth2AccessToken
@Override
public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
	OAuth2AccessToken token = super.grant(grantType, tokenRequest);
	if (token != null) {
		DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken(token);
		// The spec says that client credentials should not be allowed to get a refresh token
		if (!allowRefresh) {
			norefresh.setRefreshToken(null);
		}
		token = norefresh;
	}
	return token;
}

ImplicitTokenGranter(隐式Token授权)

你可能感兴趣的:(#,SpringCloud,spring,java,servlet)