目录
运行流程
申请授权码
跳转至登录页面
跳转至授权页面
跳转至回调地址
申请TOKEN
访问资源服务器
代码示例
创建认证服务器
配置SecuityConfig
配置资源服务器
申请授权码程序流程
EnableAuthorizationServer
AuthorizationServerEndpointsConfiguration
AuthorizationEndpoint
认证成功处理器
展示授权确认页面
授权页面路径
授权页面
生成授权码
getAuthorizationCodeResponse
generateCode
createAuthorizationCode
generate
申请TOKEN程序流程
EnableAuthorizationServer
AuthorizationServerEndpointsConfiguration
TokenEndpoint
客户端认证
BasicAuthenticationFilter
BasicAuthenticationConverter
自定义ClientDetailsService
生成Access Token
访问资源服务器程序流程
EnableResourceServer
ResourceServerConfiguration
OAuth2AuthenticationProcessingFilter
本篇文章我们来研究spring-security框架如何实现OAuth2的授权码授权模式,该模式的客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。
运行流程
申请授权码
http://serverIP:PORT/oauth/authorize?response_type=code&client_id=web&redirect_uri=https://www.client.com&scope=server&state=abc
response_type:表示授权类型,必选项,此处的值固定为“code”
client_id:表示客户端的ID,必选项
redirect_uri:表示重定向URI,可选项
scope:表示申请的权限范围,可选项
state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
跳转至登录页面
第一次访问上面地址申请授权码,会先跳转至自定义登录页面。
如下所示:

跳转至授权页面

输入账号密码,认证成功后跳转至授权页面。
如果希望默认就是授权,不需要跳至授权页面,那么sys_oauth_client_detail表中autoapprove设置为true(值:1)即可。
特别说明的是:第一次授权以后获取授权码code,之后获取授权码code直接收入账号、密码后立即返回授权码code,并不会再一次要求用户进入授权页面(base-grant.html)允许、拒绝选择操作。
跳转至回调地址
同意之后,跳转到我们设定的redirect_uri=https://www.client.com,同时附带授权码code。如下所示:
https://client.com/cb?code=xyz&state=abc
申请TOKEN
根据上面获取的授权码code=xyz获取访问的token。调用过程中相当于客户端(client)在与授权服务器进行交互。
如下所示:
curl --location --request POST 'http://serverIP:PORT/oauth/token?code=xyz&grant_type=authorization_code&redirect_uri=https://www.client.com&scope=read' \ --header 'Authorization: Basic Y2xpZW50LWNvZGU6c2VjcmV0LWNvZGU=' |
访问资源服务器
到这里,就可以正常访问相关的服务器资源了。
代码示例
创建认证服务器
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() // 基于内存模式 .withClient("web") // 客户端唯一表示 .secret(passwordEncoder.encode("123456")) .authorizedGrantTypes("authorization_code") // 授权模式:授权码模式 .redirectUris("https://www.client.com") // 回调地址 .scopes("read") // 作用域 .authorities("CLIENT"); // 权限 } /** * 认证服务器配置 * 资源服务器配置了tokenservice后,访问check_token、token_key时不需要授权访问 **/ @Override public void configure(AuthorizationServerSecurityConfigurer server) { server.tokenKeyAccess("permitAll()") // 获取tokenkey的接口不需要授权访问 .checkTokenAccess("permitAll()") // 进行token校验的接口不需要授权访问 .allowFormAuthenticationForClients(); } } |
- 通过配置 @Configuration和@EnableAuthorizationServer 注解,开启授权服务;
- 重写AuthorizationServerConfigurerAdapter 自定义认证服务器配置;
- configure(ClientDetailsServiceConfigurer clients) 配置客户端(第三方)的账号信息。
配置SecuityConfig
@Configuration @EnableWebSecurity public class SecuityConfig extends WebSecurityConfigurerAdapter { /** * Spring security5要求密码必须使用加密算法加密后存储,在登录验证时,Spring security会将获得的密码进行加密后再和数据库中加密后的密码进行对比,这里使用更加安全的bcrypt加密方式。 **/ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin().permitAll() .and() .logout().permitAll(); http.csrf().disable(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); auth.inMemoryAuthentication().withUser("admin") .password(encoder.encode("123456")) .authorities("ROLE_ADMIN"); } } |
配置资源服务器
@EnableResourceServer是Spring Security OAuth2 框架中的一个注解,用于标识当前应用是一个OAuth2 资源服务器(Resource Server)。
- 表示该服务接收携带 OAuth2 Token 的请求,并根据 Token 权限来保护资源。
- 它不处理用户登录、Token 发放等任务,而是验证客户端请求中携带的 Access Token 是否合法,并据此控制访问权限。
@Configuration @EnableResourceServer public class ResourceConfig extends ResourceServerConfigurerAdapter { // token校验地址 授权服务器地址 private static final String URL_CHECK_TOKEN = "http://localhost:9090/oauth/check_token"; // 分配给资源服务器的clientId private static final String R_CLIENT_ID = "client-code"; // 分配给资源服务器的SECRET private static final String R_SECRET = "secret-code"; @Bean public RemoteTokenServices remoteTokenServices () { final RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); remoteTokenServices.setCheckTokenEndpointUrl(URL_CHECK_TOKEN); remoteTokenServices.setClientId(R_CLIENT_ID); remoteTokenServices.setClientSecret(R_SECRET); return remoteTokenServices; } /** * 资源服务器访问配置 **/ @Override public void configure(HttpSecurity http) throws Exception{ // 创建session策略 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); // 所有请求都要授权 http.authorizeRequests().anyRequest().authenticated(); } /** * 资源服务器配置 **/ @Override public void configure(ResourceServerSecurityConfigurer resource) { resource.resourceId("test") // 设置 资源服务器id .stateless(true) // .tokenServices(remoteTokenServices()); // 通过远程调用的方式进行token校验;也可以使用tokenstore、tokenExtractor的方式进行校验 } } |
- 注解@Configuration和@EnableResourceServer开启资源服务器;
- 重写ResourceServerConfigurerAdapter 自定义资源服务器配置;
- 配置configure(HttpSecurity http)方法,这里可以代替Spring Security同名方法配置,开启所有请求需要授权才可访问;
- configure(ResourceServerSecurityConfigurer resource)资源配置相关,这里配置了resourId/tokenService通过远程调用方式进行token校验。
申请授权码程序流程
点击示例“创建认证服务器”的注解EnableAuthorizationServer,如下所示:
EnableAuthorizationServer
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer { } |
点击注解AuthorizationServerEndpointsConfiguration,如下所示:
AuthorizationServerEndpointsConfiguration
public class AuthorizationServerEndpointsConfiguration { ... ... @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")); authorizationEndpoint.setTokenGranter(tokenGranter()); authorizationEndpoint.setClientDetailsService(clientDetailsService); authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices()); authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory()); authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator()); authorizationEndpoint.setUserApprovalHandler(userApprovalHandler()); authorizationEndpoint.setRedirectResolver(redirectResolver()); return authorizationEndpoint; } |
在这里,可以看到,定义一个AuthorizationEndpoint,这个类里定义了/oauth/authorize接口,点击AuthorizationEndpoint类,如下所示:
AuthorizationEndpoint
public class AuthorizationEndpoint extends AbstractEndpoint { ... ... @RequestMapping({"/oauth/authorize"}) public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters, SessionStatus sessionStatus, Principal principal) { AuthorizationRequest authorizationRequest = this.getOAuth2RequestFactory().createAuthorizationRequest(parameters); Set<String> responseTypes = authorizationRequest.getResponseTypes(); if (!responseTypes.contains("token") && !responseTypes.contains("code")) { throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes); } else if (authorizationRequest.getClientId() == null) { throw new InvalidClientException("A client id must be provided"); } else { try { if (principal instanceof Authentication && ((Authentication)principal).isAuthenticated()) { ClientDetails client = this.getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId()); String redirectUriParameter = (String)authorizationRequest.getRequestParameters().get("redirect_uri"); String resolvedRedirect = this.redirectResolver.resolveRedirect(redirectUriParameter, client); if (!StringUtils.hasText(resolvedRedirect)) { throw new RedirectMismatchException("A redirectUri must be either supplied or preconfigured in the ClientDetails"); } else { authorizationRequest.setRedirectUri(resolvedRedirect); this.oauth2RequestValidator.validateScope(authorizationRequest, client); authorizationRequest = this.userApprovalHandler.checkForPreApproval(authorizationRequest, (Authentication)principal); boolean approved = this.userApprovalHandler.isApproved(authorizationRequest, (Authentication)principal); authorizationRequest.setApproved(approved); if (authorizationRequest.isApproved()) { if (responseTypes.contains("token")) { return this.getImplicitGrantResponse(authorizationRequest); } if (responseTypes.contains("code")) { return new ModelAndView(this.getAuthorizationCodeResponse(authorizationRequest, (Authentication)principal)); } } model.put("authorizationRequest", authorizationRequest); model.put("org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.ORIGINAL_AUTHORIZATION_REQUEST", this.unmodifiableMap(authorizationRequest)); return this.getUserApprovalPageResponse(model, authorizationRequest, (Authentication)principal); } } else { throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed."); } } catch (RuntimeException var11) { sessionStatus.setComplete(); throw var11; } } } |
在这里,主要会进行如下操作:
在进入/oauth/authorize接口前,请求会经过 Spring Security 的过滤器链(如 `UsernamePasswordAuthenticationFilter` 等)。
如果用户未认证,UsernamePasswordAuthenticationFilter会重定向到配置好的登录页(如 `/login`)。
登录成功后,再重新进入 `/oauth/authorize` 流程。
因此,在/oauth/authorize接口里,只需要判断用户是否已登录,未登录时就抛出 InsufficientAuthenticationException异常,并提示: User must be authenticated with Spring Security before authorization can be completed。
用于让用户确认是否允许客户端访问其资源。
若用户确认授权,则生成一个临时的授权码并重定向回客户端指定的redirect_uri。
认证成功处理器
用户登录成功后,系统是如何重新进入/oauth/authorize接口的呢?
SavedRequestAwareAuthenticationSuccessHandler是 Spring Security 中的一个默认的认证成功处理器AuthenticationSuccessHandler,在spring-security-oauth和普通 Spring Security 应用中广泛使用。
在用户登录成功后,重定向到用户最初请求的受保护 URL(即“被拦截时保存的请求”),而不是固定跳转到首页或某个指定页面。
当未登录用户访问一个受保护资源时,Spring Security 会触发认证流程,并将原始请求保存到RequestCache(默认实现为HttpSessionRequestCache)。
登录成功后,SavedRequestAwareAuthenticationSuccessHandler会从缓存中取出这个原始请求,并跳转到该 URL。
用户访问 /oauth/authorize,被拦截并跳转到/login,登录成功后又跳回 /oauth/authorize。
展示授权确认页面
点击AuthorizationEndpoint类的getUserApprovalPageResponse,如下所示:
private ModelAndView getUserApprovalPageResponse(Map<String, Object> model, AuthorizationRequest authorizationRequest, Authentication principal) { if (logger.isDebugEnabled()) { logger.debug("Loading user approval page: " + userApprovalPage); } model.putAll(userApprovalHandler.getUserApprovalRequest(authorizationRequest, principal)); return new ModelAndView(userApprovalPage, model); } |
在这里,返回userApprovalPage所指定的页面。
点击userApprovalPage,如下所示:
授权页面路径
private String userApprovalPage = "forward:/oauth/confirm_access"; |
在这里,将授权页面路径指定为/oauth/confirm_access。
授权页面
点击/oauth/confirm_access,如下所示:
<form id='confirmationForm' name='confirmationForm' action='authorize' method='post'> <input name='user_oauth_approval' value='true' type='hidden'/><ul><li> <div class='form-group'>scope.all: <input type='radio' name='scope.all' value='true' checked>Approve</input> <input type='radio' name='scope.all' value='false'>Deny</input> </div></li></ul> <label> <input name='authorize' value='Authorize' type='submit'/> </label> </form> |
生成授权码
点击AuthorizationEndpoint类的getAuthorizationCodeResponse,如下所示:
getAuthorizationCodeResponse
private View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) { try { return new RedirectView(getSuccessfulRedirect(authorizationRequest, generateCode(authorizationRequest, authUser)), false, true, false); } catch (OAuth2Exception e) { return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false); } } |
点击generateCode,如下所示:
generateCode
private String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) throws AuthenticationException { try { OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest); OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication); String code = authorizationCodeServices.createAuthorizationCode(combinedAuth); return code; } catch (OAuth2Exception e) { if (authorizationRequest.getState() != null) { e.addAdditionalInformation("state", authorizationRequest.getState()); } throw e; } } |
点击createAuthorizationCode()方法,如下所示:
createAuthorizationCode
点击generate()方法,如下所示:
public String createAuthorizationCode(OAuth2Authentication authentication) { String code = generator.generate(); store(code, authentication); return code; } |
generate
private Random random = new SecureRandom(); public String generate() { byte[] verifierBytes = new byte[length]; random.nextBytes(verifierBytes); return getAuthorizationCodeString(verifierBytes); } |
在这里,通过SecureRandom类生成随机码作为授权码。
申请TOKEN程序流程
点击示例“创建认证服务器”的注解EnableAuthorizationServer,如下所示:
EnableAuthorizationServer
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer { } |
点击注解AuthorizationServerEndpointsConfiguration,如下所示:
AuthorizationServerEndpointsConfiguration
public class AuthorizationServerEndpointsConfiguration { ... ... @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 Bean对象,并且自定义了clientDetailsService属性。
这个类里定义了/oauth/token接口,点击TokenEndpoint类,如下所示:
TokenEndpoint
public class TokenEndpoint extends AbstractEndpoint { ... ... @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."); } String clientId = getClientId(principal); ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); 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"); } } if (authenticatedClient != null) { oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); } if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } 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()); } } 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))); } OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); } return getResponse(token); } |
客户端认证
调用oauth/token接口申请TOKEN时,是需要在header里带上Authorization信息的,如:Authorization: Basic Y2xpZW50LWNvZGU6c2VjcmV0LWNvZGU=。因此,服务端需要对这些进行进行认证。
服务端的认证工作是由过滤器BasicAuthenticationFilter 执行,点击BasicAuthenticationFilter类,如下所示:
BasicAuthenticationFilter
public class BasicAuthenticationFilter extends OncePerRequestFilter { ... ... @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { final boolean debug = this.logger.isDebugEnabled(); try { UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request); if (authRequest == null) { chain.doFilter(request, response); return; } |
点击convert()方法,如下所示:
BasicAuthenticationConverter
public class BasicAuthenticationConverter implements AuthenticationConverter { ... ... @Override public UsernamePasswordAuthenticationToken convert(HttpServletRequest request) { String header = request.getHeader(AUTHORIZATION); header = header.trim(); if (!StringUtils.startsWithIgnoreCase(header, AUTHENTICATION_SCHEME_BASIC)) { return null; } if (header.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) { throw new BadCredentialsException("Empty basic authentication token"); } byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8); byte[] decoded; try { decoded = Base64.getDecoder().decode(base64Token); } ... ... String token = new String(decoded, getCredentialsCharset(request)); int delim = token.indexOf(":"); if (delim == -1) { throw new BadCredentialsException("Invalid basic authentication token"); } UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(token.substring(0, delim), token.substring(delim + 1)); result.setDetails(this.authenticationDetailsSource.buildDetails(request)); return result; } |
在这里,从header里的字段Authorization解析出clientId和clientSecret,为后面的认证过程提供必要的信息。
自定义ClientDetailsService
点击AuthorizationServerEndpointsConfiguration类,如下所示:
public class AuthorizationServerEndpointsConfiguration { ... ... @Autowired private ClientDetailsService clientDetailsService; ... ... @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; } |
在这里,通过注解Autowired注入ClientDetailsService 的实例,然后在实例化tokenEndpoint时,调用setClientDetailsService(clientDetailsService)将ClientDetailsService实例赋值给tokenEndpoint实例,因些,我们只需要在创建认证服务器时配置一个ClientDetailServiceImpl实例即可,如下所示:
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { ... ... @Bean // 声明ApplyClientDetailService public ClientDetailServiceImpl getClientDetails() { return new ClientDetailServiceImpl(); } |
生成Access Token
点击TokenEndpoint 类的getTokenGranter().grant()方法,如下所示:
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); } |
点击getAccessToken()方法,如下所示:
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) { return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest)); } |
点击createAccessToken()方法,如下所示:
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException { ... ... OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken); tokenStore.storeAccessToken(accessToken, authentication); // In case it was modified refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.storeRefreshToken(refreshToken, authentication); } return accessToken; } |
点击createAccessToken()方法,如下所示:
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) { 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()); return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token; } |
在这里,可以看到AccessToken是通过UUID.randomUUID().toString()的方式生成的。
访问资源服务器程序流程
点击ResourceConfig 类的注解EnableAuthorizationServer,如下所示:
EnableResourceServer
@Import(ResourceServerConfiguration.class) public @interface EnableResourceServer { } |
点击ResourceServerConfiguration类,如下所示:
ResourceServerConfiguration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered { ... ... @Override protected void configure(HttpSecurity http) throws Exception { ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer(); ResourceServerTokenServices services = resolveTokenServices(); ... ... } |
点击ResourceServerSecurityConfigurer类,如下所示:
ResourceServerSecurityConfigurer
public final class ResourceServerSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { ... ... @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http); resourcesServerFilter = new OAuth2AuthenticationProcessingFilter(); resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint); resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager); if (eventPublisher != null) { resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher); } if (tokenExtractor != null) { resourcesServerFilter.setTokenExtractor(tokenExtractor); } if (authenticationDetailsSource != null) { resourcesServerFilter.setAuthenticationDetailsSource(authenticationDetailsSource); } resourcesServerFilter = postProcess(resourcesServerFilter); resourcesServerFilter.setStateless(stateless); // @formatter:off http .authorizeRequests().expressionHandler(expressionHandler) .and() .addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class) .exceptionHandling() .accessDeniedHandler(accessDeniedHandler) .authenticationEntryPoint(authenticationEntryPoint); } |
在这里,new了一个过滤器OAuth2AuthenticationProcessingFilter对象,如下所示:
OAuth2AuthenticationProcessingFilter
public class OAuth2AuthenticationProcessingFilter implements Filter, InitializingBean { ... ... public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final boolean debug = logger.isDebugEnabled(); final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; try { Authentication authentication = tokenExtractor.extract(request); if (authentication == null) { ... ... } else { request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal()); if (authentication instanceof AbstractAuthenticationToken) { AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication; needsDetails.setDetails(authenticationDetailsSource.buildDetails(request)); } Authentication authResult = authenticationManager.authenticate(authentication); if (debug) { logger.debug("Authentication success: " + authResult); } eventPublisher.publishAuthenticationSuccess(authResult); SecurityContextHolder.getContext().setAuthentication(authResult); } } catch (OAuth2Exception failed) { ... ... } chain.doFilter(request, response); } |
在这里,会从请求对象里提取access_token,然后再对该token进行认证。
只有认证成功才能访问资源。