spring-securty-oauth2端点
在接入oauth2之后可以访问相关oauth2接口
[/oauth/authorize] [/oauth/token] [/oauth/check_token] [/oauth/confirm_access] [/oauth/token_key] [/oauth/error]
会好奇是如何实现的。其实内部是基于spring mvc
我们搭建oauth2服务器会有以下配置
@Configuration @EnableAuthorizationServer//<1>具体端点初始化在这里 public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { ....... }
<1>
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented //<2>AuthorizationServerEndpointsConfiguration 为端点初始化自动化配置类 @Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer { }
<2>
@Configuration @Import({TokenKeyEndpointRegistrar.class}) public class AuthorizationServerEndpointsConfiguration { private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer(); @Autowired private ClientDetailsService clientDetailsService; @Autowired private List<AuthorizationServerConfigurer> configurers = Collections.emptyList(); public AuthorizationServerEndpointsConfiguration() { } //<3> /oauth/authorize spring mvchandler @Bean public AuthorizationEndpoint authorizationEndpoint() throws Exception { AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint(); FrameworkEndpointHandlerMapping mapping = this.getEndpointsConfigurer().getFrameworkEndpointHandlerMapping(); authorizationEndpoint.setUserApprovalPage(this.extractPath(mapping, "/oauth/confirm_access")); authorizationEndpoint.setProviderExceptionHandler(this.exceptionTranslator()); authorizationEndpoint.setErrorPage(this.extractPath(mapping, "/oauth/error")); authorizationEndpoint.setTokenGranter(this.tokenGranter()); authorizationEndpoint.setClientDetailsService(this.clientDetailsService); authorizationEndpoint.setAuthorizationCodeServices(this.authorizationCodeServices()); authorizationEndpoint.setOAuth2RequestFactory(this.oauth2RequestFactory()); authorizationEndpoint.setOAuth2RequestValidator(this.oauth2RequestValidator()); authorizationEndpoint.setUserApprovalHandler(this.userApprovalHandler()); return authorizationEndpoint; } // /oauth/token spring mvchandler @Bean public TokenEndpoint tokenEndpoint() throws Exception { TokenEndpoint tokenEndpoint = new TokenEndpoint(); tokenEndpoint.setClientDetailsService(this.clientDetailsService); tokenEndpoint.setProviderExceptionHandler(this.exceptionTranslator()); tokenEndpoint.setTokenGranter(this.tokenGranter()); tokenEndpoint.setOAuth2RequestFactory(this.oauth2RequestFactory()); tokenEndpoint.setOAuth2RequestValidator(this.oauth2RequestValidator()); tokenEndpoint.setAllowedRequestMethods(this.allowedTokenEndpointRequestMethods()); return tokenEndpoint; } // /oauth/check_token spring mvchandler @Bean public CheckTokenEndpoint checkTokenEndpoint() { CheckTokenEndpoint endpoint = new CheckTokenEndpoint(this.getEndpointsConfigurer().getResourceServerTokenServices()); endpoint.setAccessTokenConverter(this.getEndpointsConfigurer().getAccessTokenConverter()); endpoint.setExceptionTranslator(this.exceptionTranslator()); return endpoint; } // /oauth/confirm_access 端点 @Bean public WhitelabelApprovalEndpoint whitelabelApprovalEndpoint() { return new WhitelabelApprovalEndpoint(); } // /oauth/error 端点 @Bean public WhitelabelErrorEndpoint whitelabelErrorEndpoint() { return new WhitelabelErrorEndpoint(); } //<3>spring mvc RequestMappingHandlerMapping 扩展暴露端点 @Bean public FrameworkEndpointHandlerMapping oauth2EndpointHandlerMapping() throws Exception { return this.getEndpointsConfigurer().getFrameworkEndpointHandlerMapping(); } }
<4>
public class FrameworkEndpointHandlerMapping extends RequestMappingHandlerMapping { //<5>匹配端点 protected boolean isHandler(Class<?> beanType) { return AnnotationUtils.findAnnotation(beanType, FrameworkEndpoint.class) != null; } }
<5>
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.security.oauth2.provider.endpoint; import java.security.Principal; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException; import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.ClientRegistrationException; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2RequestValidator; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @FrameworkEndpoint //oauth2端点标记 public class TokenEndpoint extends AbstractEndpoint { private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator(); private Set<HttpMethod> allowedRequestMethods; public TokenEndpoint() { this.allowedRequestMethods = new HashSet(Arrays.asList(HttpMethod.POST)); } @RequestMapping( value = {"/oauth/token"}, method = {RequestMethod.GET} ) public ResponseEntity<OAuth2AccessToken> getAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { if (!this.allowedRequestMethods.contains(HttpMethod.GET)) { throw new HttpRequestMethodNotSupportedException("GET"); } else { return this.postAccessToken(principal, parameters); } } @RequestMapping( value = {"/oauth/token"}, method = {RequestMethod.POST} ) public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException { if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter."); } else { String clientId = this.getClientId(principal); ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId); TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) { throw new InvalidClientException("Given client ID does not match authenticated client"); } else { if (authenticatedClient != null) { this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); } if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } else if (tokenRequest.getGrantType().equals("implicit")) { throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); } else { if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) { this.logger.debug("Clearing scope of incoming token request"); tokenRequest.setScope(Collections.emptySet()); } if (this.isRefreshTokenRequest(parameters)) { tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope"))); } OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type"); } else { return this.getResponse(token); } } } } } protected String getClientId(Principal principal) { Authentication client = (Authentication)principal; if (!client.isAuthenticated()) { throw new InsufficientAuthenticationException("The client is not authenticated."); } else { String clientId = client.getName(); if (client instanceof OAuth2Authentication) { clientId = ((OAuth2Authentication)client).getOAuth2Request().getClientId(); } return clientId; } } @ExceptionHandler({HttpRequestMethodNotSupportedException.class}) public ResponseEntity<OAuth2Exception> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception { if (this.logger.isInfoEnabled()) { this.logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); } return this.getExceptionTranslator().translate(e); } @ExceptionHandler({Exception.class}) public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception { if (this.logger.isErrorEnabled()) { this.logger.error("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage(), e); } return this.getExceptionTranslator().translate(e); } @ExceptionHandler({ClientRegistrationException.class}) public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception { if (this.logger.isWarnEnabled()) { this.logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); } return this.getExceptionTranslator().translate(new BadClientCredentialsException()); } @ExceptionHandler({OAuth2Exception.class}) public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception { if (this.logger.isWarnEnabled()) { this.logger.warn("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage()); } return this.getExceptionTranslator().translate(e); } private ResponseEntity<OAuth2AccessToken> getResponse(OAuth2AccessToken accessToken) { HttpHeaders headers = new HttpHeaders(); headers.set("Cache-Control", "no-store"); headers.set("Pragma", "no-cache"); return new ResponseEntity(accessToken, headers, HttpStatus.OK); } private boolean isRefreshTokenRequest(Map<String, String> parameters) { return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null; } private boolean isAuthCodeRequest(Map<String, String> parameters) { return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null; } public void setOAuth2RequestValidator(OAuth2RequestValidator oAuth2RequestValidator) { this.oAuth2RequestValidator = oAuth2RequestValidator; } public void setAllowedRequestMethods(Set<HttpMethod> allowedRequestMethods) { this.allowedRequestMethods = allowedRequestMethods; } }
redis key相关
access:{token} |
token与授权token映射 | 1天 | 登录用户数 |
根据token获取用户的 授权token信息 "access_token": "bb6ecfd7-3895-4394-b0cd-2b1fbcd9c0d3", |
||
2 |
auth_to_access:{userName+CLIENT_ID+SCOPE} |
用户信息与授权token映射 | 1天 | 登录用户数 | 根据用户信息获取 授权token信息(自定义)
"access_token": "bb6ecfd7-3895-4394-b0cd-2b1fbcd9c0d3", |
|
3 |
auth:{token} |
用户的信息与授权token映射 | 1天 | 登录用户数 | 根据token获取用户基础信息,登录信息,权限等信息 | |
4 |
refresh_auth:{refreshToken} |
refreshToken与用户信息映射 | 暂时未用 | |||
5 |
access_to_refresh:{refreshToken} |
通过accetoken查找refreshtoken与token映射 | 1天 | 暂时未用 | ||
6 |
|
1天 | 暂时未用 | |||
7 |
refresh_to_access:{refreshToken} |
refreshtoken与token映射 | 1天 | 登录用户数 | 用于刷新token | |
8 |
client_id_to_access:{clientId} |
指定应用的关联的授权token集合 | 1天 | 登录用户数 | 清除应用下的所有token | |
9 |
uname_to_access:{username+clientId} |
username+clientId用户的授权令牌集合 | 1天 | 登录用户数 | 用户可以申请指定应用的各种资源访问token,这里统一记录。 |