Spring Security怎么使用OAuth认证?


theme: v-green

OAuth2简介

是什么?

是目前最流行的授权机制,用来授权第三方应用,获取用户数据。

简单说,OAuth 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

qq授权登录, 微信授权登录, 微博授权登录, github授权登录等

以上这是都是它的落地实现

举个例子:

用户想通过QQ登录头条,那么头条就需要qq的账号密码, 这是不行的, 所以需要另外一种授权机制, 在qq的网站上登录, 然后告知头条部分数据(至少告知头条我这里登录成功了token), 这样头条就根据qq授权返回的信息登录头条, 等到下次, 再次访问头条后, 头条使用token代替qq的账号密码方式登录(一般不需要再次登录了, 只要验证token过期了没有)

这里讲到了 token , token一般是有过期时间的, 并且通过 token 能够拿到权限列表

令牌与密码的区别

令牌(token)与密码(password)的作用是一样的,都可以进入系统,但是有三点差异。

(1)**令牌是短期的,到期会自动失效,用户自己无法修改。**密码一般长期有效,用户不修改,就不会发生变化。

(2)**令牌可以被数据所有者撤销,会立即失效。**以上例而言,屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。

(3)**令牌有权限范围(scope),比如只能进小区的二号门。**对于网络服务来说,只读令牌就比读写令牌更安全。密码一般是完整权限。

上面这些设计,保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全。这就是 OAuth 2.0 的优点。

注意,只要知道了令牌,就能进入系统。系统一般不会再次确认身份,所以令牌必须保密,泄漏令牌与泄漏密码的后果是一样的。 这也是为什么令牌的有效期,一般都设置得很短的原因。

OAuth 2.0 对于如何颁发令牌的细节,规定得非常详细。具体来说,一共分成四种授权类型(authorization grant),即四种颁发令牌的方式,适用于不同的互联网场景。

所以也需要关注到token的安全问题, 不要泄漏了, 不然就危险了

OAuth 的核心就是向第三方应用颁发令牌。

四种授权模式

  • 授权码(authorization-code)
  • 隐藏式(implicit)
  • 密码式(password)
  • 客户端凭证(client credentials)

不管哪一种授权方式,第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个身份识别码:客户端 ID(client ID)客户端密钥(client secret)这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。

如果你做过微信授权登录一定会熟悉这个流程

第一种授权方式:授权码

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

下面以博客使用qq授权登录的过程为例

里面的qq网址不对应实际qq网址

下面才是qq授权的实际网址

https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=112233445&redirect_uri=https%3A%2F%2Fwww.52pojie.cn%2Fconnect.php%3Fmod%3Dlogin%26op%3Dcallback%26referer%3Dindex.php&state=6d9aaa4d7b6777779d1a36b76c2ddddd&scope=get_user_info%2Cadd_share%2Cadd_t%2Cadd_pic_t%2Cget_repost_list
  1. 博客提供一个网址给用户, 网址内容:

    1. https://qq.com/oauth/authorize?
        response_type=code&
        client_id=CLIENT_ID&
        redirect_uri=CALLBACK_URL&
        scope=read
      
    2. response_type: 告知服务端返回类型, 只能是 code 或者token, 如果是code 表明采用授权码认证模式

    3. client_id:因为认证服务器要知道是哪一个应用在请求授权,所以client_id就是认证服务器给每个应用分配的id

    4. scope:需要获得哪些授权,这个参数的值是由服务提供商定义的,不能随意填写, 这是只是只读

    5. redirect_uri:重定向地址, 这里是我们博客的地址, 而且会在博客地址后面添加授权码,让博客获取code授权码, 所以这里应该填写我们博客获得code的请求地址, 假设地址是:

      1. https://blog.com/callback?code=到时候qq那边给你填写code的具体值
        
  2. 用户使用qq的账号密码在qq的授权网址上登录

  3. qq授权用户返回 code 代码, 添加到上面的 redirect_uri地址后面https://blog.com/callback?code=AUTHORIZATION_CODE , 然后重定向到我们的博客网站上面

  4. 博客服务器收到 code 授权码后, 请求新的请求去拿到 token

    1. https://qq.com/oauth/token?
       client_id=CLIENT_ID&
       client_secret=CLIENT_SECRET&
       grant_type=authorization_code&
       code=AUTHORIZATION_CODE&
       redirect_uri=CALLBACK_URL
      
    2. 博客带着在qq上申请到的client_idclient_secret, client_secret就是认证服务器给 client_id 分配的密钥, 让认证服务器知道是我们博客正在接收认证

    3. grant_type:授权类型,授权码模式默认为 authorization_code

    4. code:授权码(一次失效), 前一次请求获得的code, 这次带上 code 去拿到 token

    5. redirect_uri: 还是回调地址, 这次给你传递的 token 以及一些失效信息等数据

      1. {    
          "access_token":"ACCESS_TOKEN",
          "token_type":"bearer",
          "expires_in":2592000,
          "refresh_token":"REFRESH_TOKEN",
          "scope":"read",
          "uid":100101
        }
        
  1. 上面的 client_idclient_secret 都需要去qq那边申请, 那边可能把client_id叫做 AppId

  2. 获得授权 codetoken 这两地址中可能还有其他参数, 比如 csrf 哪种校验码等

  3. 第二步获得 token 的过程, 是客户端在后台自动发送的, 对用户不可见

第二种方式:隐藏式(简化模式)

有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)。

现在程序员搭建博客网站都流行静态网站,例如Hexo、Jekyll等,这类技术栈有一个共同的特点就是没有后端,开发者不需要将精力放在维护后端网站上,只需要专注于内容创作即可。

流程

  1. 博客网站上有一个 github 的授权登录按钮, 地址

    1. https://github.com/oauth/authorize?
        response_type=token&
        client_id=CLIENT_ID&
        redirect_uri=CALLBACK_URL&
        scope=read&
        _csrf=111
      
    2. 基本上和授权码方式一致, 除了 response_type=token, 表示直接从授权服务器直接拿 Access token, 少了授权码的过程

  2. 用户点击按钮, 跳转到 github, 让用户登录授权, 接着会跳转到 redirect_uri的地址, 地址详情大概是这样

    1. https://blog.com/callback#access_token=xxxxxxxxxxxx&token_type=bearer&expires_in=9999&_csrf=xxx
      
    2. 令牌的位置是 URL 锚点(fragment),而不是查询字符串(querystring),这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议,因此存在"中间人攻击"的风险,而浏览器跳转时,锚点不会发到服务器,就减少了泄漏令牌的风险。

  3. 接下来客户端向资源服务器发送请求,这个请求不需要携带令牌,资源服务器返回一段JS脚本,客户端执行JS脚本,提取出令牌Access Token。

这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。

第三种方式:密码式

如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

密码模式的流程比较简单:

  1. 首先用户在第三方应用的登录页面输入登录凭证(用户名/密码)

    1. https://oauth.qq.com/token?
        grant_type=password&
        username=USERNAME&
        password=PASSWORD&
        client_id=CLIENT_ID
      
  2. 第三方应用将用户的登录信息发送给授权服务器,获取到令牌Access Token

  3. 授权服务器检查用户的登录信息,如果没有问题,授权服务器以JSON的格式发送令牌Access Token给第三方应用

第四种方式:凭证式

最后一种方式是凭证式(client credentials),适用于没有前端的命令行应用,即在命令行下请求令牌。

流程:

  1. 客户端发送一个请求到授权服务器

    1. https://oauth.b.com/token?
        grant_type=client_credentials&
        client_id=CLIENT_ID&
        client_secret=CLIENT_SECRET
      
  2. 授权服务器验证通过后,会直接返回Access Token给客户端

这种方式给出的令牌,是针对客户端使用的,而不是针对用户,即有可能多个用户共享同一个令牌。

Gitee授权登录

说句实话,国内不建议使用github学习OAuth授权认证, 因为墙问题, 需要梯子

如果你没没有使用梯子。而是使用fast git或者是别的这种工具。那么可能存在SSL安全问题。这一点值得注意。

推荐国内使用码云。

别问我怎么知道的哦!!!

Gitee 网站上配置

image-20221208214353037

从这个地方入手, 配置好

image-20221208214439247

image-20221208214458877

项目配置

导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
@RestController
@Slf4j
public class HelloController {
	
	@GetMapping("hello")
	public String hello(Principal principal) {
		return "hello, " + principal.getName();
	}
	
}
spring:
  security:
    user:
      name: zhazha
      password: '{noop}123456'
    oauth2:
      client:
        registration:
          gitee:
            client-name: Gitee
            client-id: b9xxxxxxxxxxxxxxxxxxxxxxx
            client-secret: 61xxxxxxxxxxxxxxxxxxx
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
            scope:
              - user_info
          github:
            client-id: efxxxxxxxxxxxxxxxx
            client-secret: 20xxxxxxxxxxxxxxxxxxxxxxxxxx
        provider:
          gitee:
            authorization-uri: https://gitee.com/oauth/authorize
            token-uri: https://gitee.com/oauth/token
            user-info-uri: https://gitee.com/api/v5/user
            user-name-attribute: name

上面这些gitee地址可以从gitee网址拿到:

Gitee API 文档

Gitee OAuth 文档

这里有很多相关配置:

spring.security.oauth2.client.registration.[registrationId]registrationId
spring.security.oauth2.client.registration.[registrationId].client-idclientId
spring.security.oauth2.client.registration.[registrationId].client-secretclientSecret
spring.security.oauth2.client.registration.[registrationId].client-authentication-methodclientAuthenticationMethod
spring.security.oauth2.client.registration.[registrationId].authorization-grant-typeauthorizationGrantType
spring.security.oauth2.client.registration.[registrationId].redirect-uriredirectUri
spring.security.oauth2.client.registration.[registrationId].scopescopes
spring.security.oauth2.client.registration.[registrationId].client-nameclientName
spring.security.oauth2.client.provider.[providerId].authorization-uriproviderDetails.authorizationUri
spring.security.oauth2.client.provider.[providerId].token-uriproviderDetails.tokenUri
spring.security.oauth2.client.provider.[providerId].jwk-set-uriproviderDetails.jwkSetUri
spring.security.oauth2.client.provider.[providerId].issuer-uriproviderDetails.issuerUri
spring.security.oauth2.client.provider.[providerId].user-info-uriproviderDetails.userInfoEndpoint.uri
spring.security.oauth2.client.provider.[providerId].user-info-authentication-methodproviderDetails.userInfoEndpoint.authenticationMethod
spring.security.oauth2.client.provider.[providerId].user-name-attributeproviderDetails.userInfoEndpoint.userNameAttributeName

当你在看官方案例或者网上其他案例时会发现他们可以直接使用 facebookgithub 的配置, 为什么呢?

因为他有默认配置CommonOAuth2Provider, 配置了GOOGLE, GITHUB, FACEBOOKOKTA

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;
}

直接访问 任意地址 会重定向到 gitee 地址:

https://gitee.com/login?redirect_to_url=https%3A%2F%2Fgitee.com%2Foauth%2Fauthorize%3Fresponse_type%3Dcode%26client_id%3Db99c3b81431764f6402e8a7ef90d15da5ccb1c657189bf4341b03f85c781082a%26scope%3Duser_info%26state%3DXOo_-t69onp4wXJLzNLQII9vtnhpXhTFIEgtZmKQGkI%253D%26redirect_uri%3Dhttp%3A%2F%2Flocalhost%3A8080%2Fcallback

image-20230121012531377

踩坑: 如果你设置了 FastGit 之类的工具, 可能导致 Github SSL 报错, 在认证的时候需要关闭改功能

底层都做了什么?

首先回忆下OAuth2认证的过程, 大体上是跳转, 获取code, 获取token

  1. 跳转, 用户点击使用gitee登录, 浏览器跳转到 gitee OAuth网址
  2. 获取code,

image-20221209011427525

在第一个过程中, 客户端发送了三个参数, 然后授权服务器验证成功借助客户端发送的redirect_uri携带 code 参数返回给客户端

image-20221209012226362

第二步将上面几个参数一起传递给授权服务器, 其中client-secret参数和client-id参数需要事先去授权服务器创建第三方APP

一般这个请求由客户端内部自主请求, 用户不会在浏览器看到请求 access_token 的过程

重定向过程

小白: “这里的源码好像很复杂, 我要怎么分析?”

小黑: “分析源代码不能盲目查看,而是先要确定目标。你可以给出一些目标。”

小白: " OAuth认证也是认证的一个过程。在最终也是需要返回Authentication, 但是OAuth认证的authentication有一些独特的地方。"

小白: “比如说获取用户名。这里面OAuth认证里面所有的用户信息全部都是从各个不同的支持OAuth认证的授权服务器上获取的。所以在authentication里面可能会存在map结构。这个map这个说白了就是各个授权服务器上的用户信息。”

小白: “不同的授权服务器有不同的结构。这个map结构其实是一个妥协”

小白: “紧接着由于是OAuth认证,所以它有独特的access token机制和refresh token机制。所以我们需要有另外一个地方去保存这两个属性。最好是数据库等等。”

小白: “所以可以很明显的发现,我们需要如下步骤。”

小白: “第1个步骤是想办法从授权服务器获得code代码。这个步骤的目的主要是让服务器知道,你拥有获取access tokenrefresh token的权限, 当然这中间还需要而另外两个属性clientIdclientSecret

小白: “第2步是想办法从授权服务器拿到access tokenrefresh token.(有些授权服务器没有refresh token不过没有关系。), 这个步骤主要是让 Spring security 知道自己已经认证,并且如果在access token过期时可以借助refresh token,刷新access token。”

小白: “第3步是想办法从授权服务器拿到用户信息。这个步骤主要就是这个用于存储SecurityContextHolder上下文的。”

小黑: “我们配置oauth认证的整个过程。首先我们需要在记载上配置好我们的网页地址和回调地址。并配置好需要提供的权限。这一部基本上没有什么源码分析。”

小黑: “接着我们的最终目的是什么?是拿到服务端或者说是授权服务器的token。如果是OAuth的授权码模式的话。过程比较复杂。需要clientIdclientSecret。”

小黑: “我们的第1步是想要拿到code。也就是授权服务器提供的一个code代码。”

小黑: “那我们在我们的服务器端就要组合一个授权端的网页地址。服务端通过这个地址就能够拿到授权端的code代码。”

小白: “那授权端凭什么平白无故的给你一段code代码,让你去访问他的资源呢?”

小黑: "所以服务端需要事先去授权端注册登录并拿到两个编码clientIdclientSecret, 这个编码的作用是告知授权端我的身份,我到底是谁? "

image-20221223013727776

小黑: “有了这code编码基本上是完成了80%。现在有一个问题就是授权端怎么给你提供这个code编码?”

小黑: " OAuth给出的方案是服务端给出一个重定向地址。让授权端回调,这个重定向地址并添加上code编码。在我们的服务端基本上达到了授权端的code编码了,这一步基本上完成。"

小白: “那对应着源码的哪一段呢?”

小黑: “过程其实也很简单, spring security提供了两个过滤器,一个过滤器用于重定向OAuth2AuthorizationRequestRedirectFilter,另一个过滤器用于登录OAuth2LoginAuthenticationFilter。”

小黑: “相对应的就是重定向和登录两个部分, 重定向非常简单。”

小黑: “从application的配置类上读取到从定向的URL。然后填充一下前面的http://localhost:8080和后面的{registrationId}。就完成重定向地址的组合了。”

image-20221223020306807

'{baseUrl}/login/oauth2/code/{registrationId}'
http://localhost:8080/login/oauth2/code/gitee

image-20221223015446245

小黑: “从地下地址已经组合完成了,接下来组合的是gitee地址。拿到下面的地址。”

image-20221223020319583

小黑: “将它组合成一个完整的地址,这些参数都是可以拿得到的。”

https://gitee.com/oauth/authorize?response_type=code&client_id=121212121&scope=user_info&state=3333333%3D&redirect_uri=http://localhost:8080/login/oauth2/code/gitee

小黑: “紧接着应该就是重定向过程。”

OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
if (authorizationRequest != null) {
    // 重定向
   this.sendRedirectForAuthorization(request, response, authorizationRequest);
   return;
}

小黑: “从定向型还往session里面保存了一些数据。这段数据在获取code代码的之后就会被删除。说白了就是保存OAuth2AuthorizationRequest和删除OAuth2AuthorizationRequest,整个过程就在这里。”

HttpSessionOAuth2AuthorizationRequestRepository: 
key = org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository.AUTHORIZATION_REQUEST
value = OAuth2AuthorizationRequest(的值)

image-20221223020859051

image-20221223020937097

image-20221223021021305

小黑: “到这里的话,第一是重定向的过程基本上完成了,剩下的就是用户去输入用户名密码获得code并组合获取token地址, 访问获取token的过程。”

上面源码分析过程看的比较累, 可以直接看下图

OAuth2创建获取code地址的过程

获取code并访问获取token和用户信息地址的过程

说白了就是分析OAuth2LoginAuthenticationFilter的过程

分为两个流程:

  1. 生成: spring security生成 获取 codegitee 地址
  2. 获取: 登录gitee地址获取 token 的过程

首先你登陆账户后, gitee会把code发送给你, 所以下面流程刚开始就能够从request中拿到 code, 接着就是拿着 code 生成一个新的获取 tokengitee地址, 然后访问该地址获取 access_tokenrefreshToken

OAuth2获取token的过程

源码还是比较简单的, 下载下来看就行

自定义配置

自定义ClientRegistrationRepository

我们在 gitee app上配置的回调地址如果是 http://localhost:8080/callback那么在本地客户端上也需要配置上这个地址

image-20221211200536459

记得修改地址哦, 前面我们用了http://localhost:8080/login/oauth2/code/gitee, 现在改成http://localhost:8080/callback

gitee还能配置多个回调地址:

image-20221211200740972

这个地址相当于我们的登录请求地址, 默认的loginProcessingUrl/login/oauth2/code/gitee

默认情况下, http://localhost:8080/callback请求会被当成普通请求, 只有修改loginProcessingUrl地址才能确保当前重定向到http://localhost:8080/callback地址时, 该请求会在AbstractAuthenticationProcessingFilter#doFilter方法中被认定为登录请求, 进而将请求交给OAuth2LoginAuthenticationFilter#attemptAuthentication方法去处理, 以完成登录操作.

我们可以自定义ClientRegistrationRepository:

 @Bean
 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
          .anyRequest().authenticated()
          .and()
          .oauth2Login()
          .loginPage("/login")
          .loginProcessingUrl("/callback");
    return http.build();
 }
 ​
 /**
  * 自定义 ClientRegistrationRepository
  *
  * @return
  */
 @Bean
 public ClientRegistrationRepository clientRegistrationRepository() {
    return new InMemoryClientRegistrationRepository(giteeClientRegistration());
 }
 ​
 private static ClientRegistration giteeClientRegistration() {
    return ClientRegistration.withRegistrationId("gitee")
          .clientId("b99c3b81431764f6402e8a7ef90d15da5ccb1c657189bf4341b03f85c781082a")
          .clientSecret("6176e926f9ce31d1008e8339b6d3fad1849bf3f8124c692355ec6e07efa25241")
          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
          .userNameAttributeName("name")
          .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
          .redirectUri("http://localhost:8080/callback")
          .scope("user_info")
          .authorizationUri("https://gitee.com/oauth/authorize")
          .tokenUri("https://gitee.com/oauth/token")
          .userInfoUri("https://gitee.com/api/v5/user")
          .clientName("gitee")
          .build();
 }

自定义用户

默认情况下, spring security 读取的用户信息都是存储在OAuth2User

我们可以自定义实现一个entity

 @Data
 public class GiteeOAuth2User implements OAuth2User {
 ​
    private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
    private Map<String, Object> attributes;
    private String id;
    private String name;
    private String login;
    private String avatarUrl;
    private String email;
 ​
    @Override
    public <A> A getAttribute(String name) {
       return OAuth2User.super.getAttribute(name);
    }
 ​
    @Override
    public Map<String, Object> getAttributes() {
       if (this.attributes == null) {
          this.attributes = new HashMap<>();
          this.attributes.put("id", this.getId());
          this.attributes.put("name", this.getName());
          this.attributes.put("login", this.getLogin());
          this.attributes.put("email", this.getEmail());
       }
       return attributes;
    }
 ​
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
       return this.authorities;
    }
 ​
    @Override
    public String getName() {
       return this.name;
    }
 }
 @Bean
 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeRequests()
          .anyRequest().authenticated();
 ​
    http.oauth2Login()
          .userInfoEndpoint()
          .customUserType(GiteeOAuth2User.class, "gitee") // 已弃用的方法
          .and()
          .loginProcessingUrl("/callback");
    http.oauth2Client();
    return http.build();
 }

底层根据customUserType配置的GiteeOAuth2User, 根据类型判断到底拿那个 OAuth2UserService

 OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = getOAuth2UserService();
 OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(
       accessTokenResponseClient, oauth2UserService);

上面的代码我们拿到的是CustomUserTypesOAuth2UserService

image-20221212014235335

但你会发现, 走他们的方法会存在 GiteeOAuth2User.avatarUrl属性是空的

因为借助 RestTemplate 无法转换avatar_url 和 avatarUrl

我们可以自定义:

 http.oauth2Login()
       .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(userRequest -> {
          DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();
          OAuth2User oAuth2User = defaultOAuth2UserService.loadUser(userRequest);
          Map<String, Object> attributes = oAuth2User.getAttributes();
          String jsonStr = JSONUtil.toJsonStr(attributes);
          return JSONUtil.toBean(jsonStr, GiteeOAuth2User.class);
       }))
       .loginProcessingUrl("/callback")

image-20221212014711768

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值