目录
AuthorizationServerEndpointsConfiguration
AuthorizationServerTokenServices
AuthorizationServerSecurityConfiguration
ClientDetailsServiceConfiguration
AuthorizationServerEndpointsConfiguration
引入
在引入OAuth2时,会在@Configuration注解的bean上添加@EnableAuthorizationServer注解。
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
@Deprecated
public @interface EnableAuthorizationServer {
}
@EnableAuthorizationServer注解主要引入了2个类:AuthorizationServerEndpointsConfiguration和 AuthorizationServerSecurityConfiguration,分别用于配置授权端点和鉴权配置。
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
DefaultOAuth2RequestFactory是OAuth2RequestFactory的默认实现。从参数中获取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。如果需要增加可以使用TokenEnhancer(TokenEnhancerChain实现类 )。
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;
引入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: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个具体实现: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();
//是否有权限范围
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
见上面。