前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security 6.x OAuth2登录认证源码分析

Spring Security 6.x OAuth2登录认证源码分析

原创
作者头像
fullstackyang
发布2024-06-20 00:09:37
1310
发布2024-06-20 00:09:37
举报

上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。

一、OAuth2协议简介

OAuth2协议,英文全称Open Authorization 2.0,即开放授权协议,它本身解决的问题,就是互联网中的安全信任问题,当第三方需要访问本系统内受保护资源的时候,如何对其授权以实现合法安全的访问。

举个例子,可能在物理世界里面并不存在,只是为方便说明OAuth2的工作原理。假设有某个大型商场提供了一种无卡消费的服务,用户只要在商场的账户中充值,就可以在商场中任何一家店铺进行无卡消费,此时商家作为第三方,需要访问你的无卡账户,对于用户来说,无卡账户就是一种受保护资源,它并不能随意进行访问,那么怎么解决信任问题。

首先,能够想到的是,任何一家的店铺想要支持无卡消费,就必须在商场内进行登记注册,只有在册的店铺才被允许访问储值卡的账户;

其次,用户不应该在每家店铺消费时都提供用户名和密码,这样密码就存在泄露的风险,商场应该提供一种用户授权的交互方式,在店铺发起访问无卡账户时,用户只需要授权即可,姑且想象一下,在店铺中支付时,会从空气中弹出一个商场提供的确认授权页面,当然商场已经核对了商户的注册信息;

最后,一旦用户对该店铺进行了合法的授权,商场就给店铺发放一个交易凭证,店铺带着这个凭证就可以访问我的无卡账户;

当然,商场应当要保证这个交易凭证的发放是安全的,不能轻易泄露,否则就有盗刷的风险。

以上,OAuth2的工作原理大致如此,用户不用担心自己的密码暴露给了第三方,而又实现了受保护资源的授权访问,其中店铺被授权后得到凭证就是所谓的访问令牌,即access_token。OAuth2协议中最主要的一个部分就是如何获取accessToken,在OAuth2协议规范文档https://datatracker.ietf.org/doc/html/rfc6749中介绍了几种常用的授权模式:

  • 授权码模式(Authorization Code Grant):服务端通过重定向机制,将一次性的授权码code参数下发给客户端,客户端通过code获取accessToken和refreshToken,这种模式比较完整地体现了OAuth2协议的原则,流程较为复杂,但安全性最好,因此也是最常用的模式,广泛运用于各类互联网应用的登录认证场景,交互流程见下图
  • 隐式模式(Implicit Grant):也叫简化模式,没有重定向过程,一次授权请求就可以获得accessToken,通常用于浏览器脚本,例如在JavaScript脚本内发起授权请求,有令牌泄露的风险,安全性一般,另外也不支持refreshToken
  • 资源属主密码模式(Resource Owner Password Credentials Grant):用户需要在第三方应用中输入用户名和密码,上面提到过,这种模式有暴露密码的风险,安全性较差,在OAuth2官方推荐的最佳实践中,已经明确禁止使用这种模式,并且在Spring Security 高版本中也已经弃用
  • 客户端凭证模式(Client Credentials Grant):常用于设备或者可信任的应用本身,通过客户端凭证与OAuth2直接通信进行认证,对用户无感

OAuth2本身是一种协议,它不直接规定实现细节,下面主要就Spring Security框架内OAuth2客户端的源码作一定的分析,通过研究它默认的实现,为将来扩展对接其他OAuth2服务端做一定参考。

二、OAuth2登录认证

Spring Security集成了国外几个OAuth2认证服务商的默认实现,包括Google, GitHub, Facebook, 以及Okta,下面以Github为例,说明OAuth2登录认证(授权码模式)的整个交互过程。

2.1 OAuth2.0客户端配置

默认配置下,仅添加SecurityFilterChain的oauth2Login配置项即可,它主要的作用是向过滤器链中添加两个过滤器:即OAuth2AuthorizationRequestRedirectFilter和OAuth2LoginAuthenticationFilter(第2小节和第3小节会分别介绍这两个类的实现细节),他们分别负责处理两个端点:

  • /oauth2/authorization/{client},即OAuth2授权端点,用于向OAuth2服务端发起授权请求
  • /login/oauth2/code/{client},即OAuth2服务端重定向端点,用于在OAuth2服务端重定向回到本应用时接收code,从而利用code换取accessToken
代码语言:javascript
复制
@EnableWebSecurity
@Configuration
public class SpringSecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.oauth2Login(Customizer.withDefaults());
        return http.build();
    }
}

另外在application.yaml配置文件中注册Github客户端,主要是指定client-id和client-secret这两个参数

通常,client-id和client-secret等参数都需要在Github官网注册自己的应用后,才能拿到。注册的操作流程放在文末的附录中

代码语言:javascript
复制
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: *********
            client-secret: **********************

至于其他参数,例如authorization-uri,token-uri等,对于任何一个OAuth2的客户端来说都是通用的,所以都已经提前定义好了,具体可以看CommonOAuth2Provider的源码

代码语言:javascript
复制
public enum CommonOAuth2Provider {
 ...
    GITHUB {

       @Override
       public Builder getBuilder(String registrationId) {
          ClientRegistration.Builder builder = getBuilder(registrationId,
                ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);
          builder.scope("read:user");
          builder.authorizationUri("https://github.com/login/oauth/authorize");
          builder.tokenUri("https://github.com/login/oauth/access_token");
          builder.userInfoUri("https://api.github.com/user");
          builder.userNameAttributeName("id");
          builder.clientName("GitHub");
          return builder;
       }
...
}

在Spring Boot中,当我们在配置文件中添加了spring.security.oauth2.client.registration相关内容时,例如上面的github配置,就会触发自动配置以完成客户端信息的注册,配置类为OAuth2ClientRegistrationRepositoryConfiguration,其中构建过程主要由OAuth2ClientPropertiesMapper这个类完成,源码如下

代码语言:javascript
复制
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(ClientsConfiguredCondition.class)
class OAuth2ClientRegistrationRepositoryConfiguration {

    @Bean
    @ConditionalOnMissingBean(ClientRegistrationRepository.class)
    InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
       List<ClientRegistration> registrations = new ArrayList<>(
             new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
       return new InMemoryClientRegistrationRepository(registrations);
    }

}

public final class OAuth2ClientPropertiesMapper {
...
    private static ClientRegistration getClientRegistration(String registrationId,
          OAuth2ClientProperties.Registration properties, Map<String, Provider> providers) {
       Builder builder = getBuilderFromIssuerIfPossible(registrationId, properties.getProvider(), providers);
       if (builder == null) {
          builder = getBuilder(registrationId, properties.getProvider(), providers);
       }
       PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
       map.from(properties::getClientId).to(builder::clientId);
       map.from(properties::getClientSecret).to(builder::clientSecret);
       map.from(properties::getClientAuthenticationMethod)
          .as(ClientAuthenticationMethod::new)
          .to(builder::clientAuthenticationMethod);
       map.from(properties::getAuthorizationGrantType)
          .as(AuthorizationGrantType::new)
          .to(builder::authorizationGrantType);
       map.from(properties::getRedirectUri).to(builder::redirectUri);
       map.from(properties::getScope).as(StringUtils::toStringArray).to(builder::scope);
       map.from(properties::getClientName).to(builder::clientName);
       return builder.build();
    }
...    
}

OAuth2ClientPropertiesMapper#getBuilder方法会根据application.yml配置文件中配置的客户端名称,从CommonOAuth2Provider枚举类中得到对应枚举值, 即“GITHUB”,并调用其getBuilder方法返回builder对象,然后使用配置文件中的参数值进行填充,最终得到完整的客户端注册信息。

2.2 OAuth2AuthorizationRequestRedirectFilter:提交授权请求

该Filter继承自OncePerRequestFilter,用于向OAuth2协议服务端发起认证请求,核心逻辑也比较简单,其中doFilterInternal核心源码如下

代码语言:javascript
复制
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
       throws ServletException, IOException {
    try {
       OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
       if (authorizationRequest != null) {
          this.sendRedirectForAuthorization(request, response, authorizationRequest);
          return;
       }
    }
    catch (Exception ex) {
       this.unsuccessfulRedirectForAuthorization(request, response, ex);
       return;
    }
    ...
}

当请求“/oauth2/authorization/{registrationId}”端点时,authorizationRequestResolver就会解析出{registrationId}对应的值,如github,然后通过registrationId查询到对应客户端的注册信息,并通过构造器OAuth2AuthorizationRequest.Builder,创建出一个OAuth2AuthorizationRequest实例,它主要作用就是生成重定向到OAuth2.0服务端获取code的地址,对于github来说,该地址为https://github.com/login/oauth/authorize?response_type=code&client_id={client_id}&scope=read:user&state={state}&redirect_uri={redirect_uri},其中state是由客户端生成的一个随机字符串,在Spring Security框架中,使用了32位长度的Base64编码生成算法,而redirect_uri则表示期望OAuth2服务端在通过验证后重定向到本系统的地址,以便从响应中获取code之后发起认证,当然这个redirectUri需要事先注册在OAuth2服务端中,否则视为非授权的访问而拒绝。

代码语言:javascript
复制
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
    if (registrationId == null) {
       return null;
    }
    ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
    if (clientRegistration == null) {
       throw new InvalidClientRegistrationIdException("Invalid Client Registration with Id: " + registrationId);
    }
    OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration);

    String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);

    // @formatter:off
    builder.clientId(clientRegistration.getClientId())
          .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
          .redirectUri(redirectUriStr)
          .scopes(clientRegistration.getScopes())
          .state(DEFAULT_STATE_GENERATOR.generateKey());
    // @formatter:on

    this.authorizationRequestCustomizer.accept(builder);

    return builder.build();
}

而在重定向之前,为了在认证通过之后能够跳转回认证前的访问路径,需要保存当前请求的地址,在authorizationRequestRepository#saveAuthorizationRequest方法中,会将当前请求存储到session中,这样就可以在OAuth2服务端回调之后,再从session取出。

代码语言:javascript
复制
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
       OAuth2AuthorizationRequest authorizationRequest) throws IOException {
    if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
       this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
    }
    this.authorizationRedirectStrategy.sendRedirect(request, response,
          authorizationRequest.getAuthorizationRequestUri());
}

public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request,
       HttpServletResponse response) {
    Assert.notNull(request, "request cannot be null");
    Assert.notNull(response, "response cannot be null");
    if (authorizationRequest == null) {
       removeAuthorizationRequest(request, response);
       return;
    }
    String state = authorizationRequest.getState();
    Assert.hasText(state, "authorizationRequest.state cannot be empty");
    request.getSession().setAttribute(this.sessionAttributeName, authorizationRequest);
}

OAuth2服务端在接受到该请求之后,如果一切正常,则会生成一个临时的code,然后连同请求参数中state一起拼接到redirect_uri的参数中,例如https://{domain}/login/oauth2/code/github?code={code}&state={state in request},最后发起重定向,此时请求就会进入过滤器OAuth2LoginAuthenticationFilter。

2.3 OAuth2LoginAuthenticationFilter:发起认证

该过滤器继承自AbstractAuthenticationProcessingFilter,显然,它的作用主要是用于完成OAuth2的认证过程,最终生成认证对象。

具体看一下attemptAuthentication方法,这里创建出来的对象稍微有点复杂,先梳理一下引用关系:

这里,OAuth2AuthorizationRequest代表了此前提交授权的请求,上文有提到,即保存在session中的请求对象,而OAuth2AuthorizationResponse代表了OAuth2服务端重定向回来的响应,其中也封装了请求时携带过去的state参数,他们构造了一个OAuth2AuthorizationExchange对象,并连同ClientRegistration一并被封装到了OAuth2LoginAuthenticationToken对象中,该对象用于在OAuth2LoginAuthenticationProvider发起认证请求时提取各种参数。

以下是源码的实现细节:

代码语言:javascript
复制
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
       throws AuthenticationException {
    MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
    // ①
    if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
       OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
       throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
       .removeAuthorizationRequest(request, response); // ②
    if (authorizationRequest == null) {
       OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
       throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
    ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
    // ③
    if (clientRegistration == null) {
       OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,
             "Client Registration not found with Id: " + registrationId, null);
       throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    // @formatter:off
    String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
          .replaceQuery(null)
          .build()
          .toUriString();
    // @formatter:on
    OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
          redirectUri);
    // ④      
    Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
    OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
          new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
    authenticationRequest.setDetails(authenticationDetails);
    // ⑤
    OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
       .getAuthenticationManager()
       .authenticate(authenticationRequest); // ⑥
    OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter
       .convert(authenticationResult); // ⑦
    Assert.notNull(oauth2Authentication, "authentication result cannot be null");
    oauth2Authentication.setDetails(authenticationDetails);
    OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
          authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
          authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
    // ⑧
    this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
    return oauth2Authentication;
}
  1. 检查URL参数是否有效,即是否包含了code和state
  2. 通过authorizationRequestRepository查询认证之前存储在session中的request对象,即authorizationRequest,同时还要将其删除,以保证一个对应的code,只能被处理一次,同时如果没有查询到对应的request对象,也不会继续执行,从而也杜绝了其他伪造的重定向请求进入系统,这一步还是比较重要的,它严格约束了一个发起授权请求和接受服务端响应必须成对匹配,否则整个OAuth2授权码流程就无法执行
  3. 这个request对象中保存了客户端的registrationId,因此可以通过clientRegistrationRepository查询到对应的客户端信息,即clientRegistration
  4. 同时再根据code和state参数,以及当前request请求的url作为redirectUri,构造出一个OAuth2AuthorizationResponse对象,即authorizationResponse
  5. 在得到clientRegistrationauthorizationRequestauthorizationResponse三个实例之后,再构造出一个OAuth2LoginAuthenticationToken实例,它便是用来发起OAuth2认证的实际对象
  6. OAuth2认证过程交由OAuth2LoginAuthenticationProvider执行(具体细节在下一节中介绍)
  7. 认证通过后,结果返回了一个OAuth2LoginAuthenticationToken对象,这个对象主要是用于封装授权码模式的认证结果,经过转换,将其principal,authorities,和clientRegistration的RegistrationId取出,最终构造出一个标准的OAuth2认证对象,即OAuth2AuthenticationToken

2.4 OAuth2LoginAuthenticationProvider:获取AccessToken

这个Provider的作用主要包括两个部分,一是请求OAuth2服务端获取AccessToken,二是获取服务端用户信息,前者委托给了OAuth2AuthorizationCodeAuthenticationProvider来执行具体请求的逻辑,而后者则通过UserService实例请求OAuth2服务端的UserInfo相关端点,获取用户信息,最后上述AccessToken相关信息,以及用户信息被封装成OAuth2LoginAuthenticationToken认证对象返回。

代码语言:javascript
复制
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    OAuth2LoginAuthenticationToken loginAuthenticationToken = (OAuth2LoginAuthenticationToken) authentication;
    ...
    OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken;
    try {
       authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider
          .authenticate(
                new OAuth2AuthorizationCodeAuthenticationToken(loginAuthenticationToken.getClientRegistration(),
                      loginAuthenticationToken.getAuthorizationExchange()));
    }
    catch (OAuth2AuthorizationException ex) {
       OAuth2Error oauth2Error = ex.getError();
       throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
    }
    OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
    Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
    OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
          loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
    Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
       .mapAuthorities(oauth2User.getAuthorities());
    OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
          loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(),
          oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken());
    authenticationResult.setDetails(loginAuthenticationToken.getDetails());
    return authenticationResult;
}

下面再看一下OAuth2LoginAuthenticationProvider#authenticate方法的源码:

代码语言:javascript
复制
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
    OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
       .getAuthorizationResponse();
    if (authorizationResponse.statusError()) {
       throw new OAuth2AuthorizationException(authorizationResponse.getError());
    }
    OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
       .getAuthorizationRequest();
    if (!authorizationResponse.getState().equals(authorizationRequest.getState())) { // ①
       OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
       throw new OAuth2AuthorizationException(oauth2Error);
    }
    OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
          new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
                authorizationCodeAuthentication.getAuthorizationExchange())); // ②
    OAuth2AuthorizationCodeAuthenticationToken authenticationResult = new OAuth2AuthorizationCodeAuthenticationToken(
          authorizationCodeAuthentication.getClientRegistration(),
          authorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(),
          accessTokenResponse.getRefreshToken(), accessTokenResponse.getAdditionalParameters());  // ③
    authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
    return authenticationResult;
}
  1. authorizationRequest中包含了请求时携带的state,而authorizationResponse中包含了OAuth2服务端重定向URL中携带的state,通过两个state参数的比较,校验是否为非法的重定向地址,如果不校验state是否一致,当其他账号正常授权时重定向的地址被另一个人点击了,就可以能发生用户在毫无察觉的情况下登录其他人账号的情况,从而导致信息泄露
  2. accessTokenResponseClient向OAuth2服务端发起认证请求,请求地址存储在ClientRegistration中的tokenUri,即https://github.com/login/oauth/access_token,请求体参数则包括code,redirect_uri,grant_type,另外还加入了Authorization的请求头,其属性值是用client_id和client_serect拼接后编码出来的一个字符串,用于向OAuth2服务端证明客户端的真实性
  3. OAuth2服务端通过认证后就会返回AccessToken,以及创建时间,过期时间等信息,最后封装成OAuth2AuthorizationCodeAuthenticationToken认证对象返回

2.5 OAuth2UserService:访问受保护资源

上文提到,需要请求OAuth2服务端获取用户信息,用户信息是服务端保护的资源,包含了在Github中个人账号的各类属性,例如id,用户名,头像,主页地址等等,因此这里需要携带AccessToken才能访问,下面看一下具体的执行过程

代码语言:javascript
复制
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
 ...
    String userNameAttributeName = userRequest.getClientRegistration()
       .getProviderDetails()
       .getUserInfoEndpoint()
       .getUserNameAttributeName();
    if (!StringUtils.hasText(userNameAttributeName)) {
       OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
             "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
                   + userRequest.getClientRegistration().getRegistrationId(),
             null);
       throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    }
    RequestEntity<?> request = this.requestEntityConverter.convert(userRequest); // 关键代码
    ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
    Map<String, Object> userAttributes = response.getBody();
    Set<GrantedAuthority> authorities = new LinkedHashSet<>();
    authorities.add(new OAuth2UserAuthority(userAttributes));
    OAuth2AccessToken token = userRequest.getAccessToken();
    for (String authority : token.getScopes()) {
       authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
    }
    return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}

public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
    ClientRegistration clientRegistration = userRequest.getClientRegistration();
    HttpMethod httpMethod = getHttpMethod(clientRegistration);
    HttpHeaders headers = new HttpHeaders();
    headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
    URI uri = UriComponentsBuilder
       .fromUriString(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
       .build()
       .toUri();

    RequestEntity<?> request;
    if (HttpMethod.POST.equals(httpMethod)) {
       headers.setContentType(DEFAULT_CONTENT_TYPE);
       MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
       formParameters.add(OAuth2ParameterNames.ACCESS_TOKEN, userRequest.getAccessToken().getTokenValue());
       request = new RequestEntity<>(formParameters, headers, httpMethod, uri);
    }
    else {
       headers.setBearerAuth(userRequest.getAccessToken().getTokenValue());
       request = new RequestEntity<>(headers, httpMethod, uri);
    }

    return request;
}

其中生成请求对象的关键代码在OAuth2UserRequestEntityConverter#convert方法中:

  • 用户信息的端点同样也是由clientRegistration提供,即https://api.github.com/user
  • accessToken作为参数,如果是GET方法,则被置于Header中的"Authorization"属性中,并按照规范添加"Bearer "的前缀,如果是POST,则被放在请求表单参数"access_token"中,此处为GET方法

有关更多访问受保护资源的方法,将在下一篇文章中介绍。

三、附录

3.1 在Github中注册一个新的OAuth2客户端

注册地址为https://github.com/settings/developers,点击右上方“new OAuth App”,就会跳转到注册页面

3.2 填写表单

其中,必填项包括“应用名称”,“主页地址”,“授权回调地址”即上文提到的redirect_uri参数,最后点击“Register Application”即可完成客户端注册

3.3 查看客户端信息

注册完成之后就可以进入客户端信息页面,此时client-id已经生成好了,还需要生成一串client-secret,点击“Generate a new client secret”即可生成,此时务必复制并保存该字符串,后面就无法再次查看了,只能再生成一个新的。

至此,客户端基本信息就注册完成了。

3.4 官方文档

如有其他问题,也可以参考其官方文档

https://docs.github.com/zh/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、OAuth2协议简介
  • 二、OAuth2登录认证
    • 2.1 OAuth2.0客户端配置
      • 2.2 OAuth2AuthorizationRequestRedirectFilter:提交授权请求
        • 2.3 OAuth2LoginAuthenticationFilter:发起认证
          • 2.4 OAuth2LoginAuthenticationProvider:获取AccessToken
            • 2.5 OAuth2UserService:访问受保护资源
            • 三、附录
              • 3.1 在Github中注册一个新的OAuth2客户端
                • 3.2 填写表单
                  • 3.3 查看客户端信息
                    • 3.4 官方文档
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档