Spring Security OAuth2,认证原理与流程。
ClientCredentialsTokenEndpointFilter
完成客户端身份认证TokenEndpoint
或 AuthorizationEndpoint
完成。授权服务安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
客户端详情配置
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
}
授权服务端点配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
}
默认OAuth2授权服务配置,可以作为参考
在这个注解中引入了两个配置类AuthorizationServerEndpointsConfiguration(授权服务站点配置)和AuthorizationServerSecurityConfiguration(授权服务安全配置)
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
@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;
}
@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;
}
ClientCredentialsTokenEndpointFilter
完成客户端身份认证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);
}
@Bean
public CheckTokenEndpoint checkTokenEndpoint() {
CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
endpoint.setExceptionTranslator(exceptionTranslator());
return endpoint;
}
主要完成功能是安全配置
主要功能: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;
}
/oauth/token
client_id
和 client_secret
的数据,转换为 UsernamePasswordAuthenticationToken
private TokenStore tokenStore() {
if (tokenStore == null) {
if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
}
else {
this.tokenStore = new InMemoryTokenStore();
}
}
return this.tokenStore;
}
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;
}
List tokenGranters
通过代理方式、循环tokenGranters,根据对应的授权模式,找到指定的TokenGranter完成Token授权模式的选择。在执行方法如下:
OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest)
/*
* 授权服务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
授权
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);
}
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}
AuthorizationCodeServices
/*
* 授权类型:password
* 身份认证管理
* /
AuthenticationManager
验证授权类型
@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);
}
@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;
}