当前位置: 首页 > news >正文

spring-security-oauth2系列:授权码授权模式

目录

运行流程

申请授权码

跳转至登录页面

跳转至授权页面

跳转至回调地址

申请TOKEN

访问资源服务器

代码示例

创建认证服务器

配置SecuityConfig

配置资源服务器

申请授权码程序流程

EnableAuthorizationServer

AuthorizationServerEndpointsConfiguration

​​​​​​​AuthorizationEndpoint

​​​​​​​认证成功处理器

​​​​​​​展示授权确认页面

​​​​​​​getUserApprovalPageResponse

​​​​​​​授权页面路径

​​​​​​​授权页面

​​​​​​​生成授权码

​​​​​​​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();

}

}

  1. 通过配置 @Configuration和@EnableAuthorizationServer 注解,开启授权服务;
  2. 重写AuthorizationServerConfigurerAdapter 自定义认证服务器配置;
  3. 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)。

  1. 表示该服务接收携带 OAuth2 Token 的请求,并根据 Token 权限来保护资源。
  2. 它不处理用户登录、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的方式进行校验

    }

}

  1. 注解@Configuration和@EnableResourceServer开启资源服务器;
  2. 重写ResourceServerConfigurerAdapter 自定义资源服务器配置;
  3. 配置configure(HttpSecurity http)方法,这里可以代替Spring Security同名方法配置,开启所有请求需要授权才可访问;
  4. 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,如下所示:

​​​​​​​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进行认证。

       只有认证成功才能访问资源。

http://www.lryc.cn/news/574730.html

相关文章:

  • F5推出AI网关,赋能企业化解大模型应用风险
  • VSCode源码解析-程序的启动逻辑
  • 深度学习在智能机器人导航中的创新应用与未来趋势
  • 分布式训练中的随机种子策略:深入理解与实践指南
  • Spring Boot 切面编程(AOP)详细教程
  • 战地2042(战地风云)因安全启动(Secure Boot)无法启动的解决方案以及其他常见的启动或闪退问题
  • 3D看房实现房屋的切换
  • 五种 IO 模式的简单介绍 -- 阻塞 IO,非阻塞 IO,信号驱动 IO,IO 多路复用,异步 IO
  • Spring Data REST极速构建REST API
  • 【ArcGIS】土地资源单项评价
  • API 调试工具校验 JSON Mock 接口(二):有参验证
  • 四色(定理/猜想)染色算法小软件Version1.11 2025.6.24 开发者:孝感动天/卧冰求鲤
  • 神经网络的本质 逻辑回归 python的动态展示
  • 蓝桥杯嵌入式学习(cubemxkeil5)
  • 从零开始学习Spring Cloud Alibaba (一)
  • PYTHON从入门到实践4-数据类型
  • 大模型时代的创业机遇
  • 快速搭建企业级私有仓库:Docker + Nexus3 私服指南
  • 数据结构知识点总结--绪论
  • 02-StarRocks数据导入导出FAQ
  • 域名 SSL证书和IP SSL证书有什么区别?
  • 15:00开始面试,15:06就出来了,问的问题有点变态。。。
  • OSS大数据分析集成:MaxCompute直读OSS外部表优化查询性能(减少数据迁移的ETL成本)
  • 内存泄漏系列专题分析之二十四:内存泄漏测试Camera相机进程内存指标分布report概述
  • C++【生存游戏】开发:荒岛往事 第一期
  • 机器学习×第十三卷:集成学习上篇——她不再独断,而是召集小队贴贴你
  • Leetcode-2563. 统计公平数对的数目
  • prometheus 配置邮件告警
  • Unity2D 街机风太空射击游戏 学习记录 #13 射击频率道具 最高分
  • 如何使typora图片不居中留白?