【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

news2024/11/14 17:21:35

之前搞 oauth 登录一直没有搞好,客户端、授权服务端、资源端一起搞对于我刚接触的小菜鸡来说,难度有点大。
然后就先搞了个 Client 端对接 Github 登录。 网上关于 Github 登录的资料有很多,而且框架对 Github 集成的也很好,配置起来并不麻烦。

对接Github登录

首先在 Github 上申请注册一个 oauth application,填写 callback url得到 client ID和 secret
默认的callback url 格式:{baseUrl}/login/oauth2/code/{registrationId}

在这里对接 GIthub 的默认重定向的地址就是http://xxxx/api/login/oauth2/code/github
如果对接其他的第三方就将 registrationId 改成 和我们配置文件中的一致即可

1、作为 Client 端只需要引入依赖

   implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

2、然后在applicatiom.yml中进行相关配置

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: 56ca77ae71xxxxxxx
            clientSecret: e1aa08298c5d0f5f9c35414355666b81xxxxxxx
            scope:
              - user:email
              - read:user

3、Create OAuth2User and OAuth2UserService Classes

主要用来定义 授权用户 的数据结构 和 加载 授权用户的相关数据

3.1、创建一个实体类去实现 OAuth2User 重写里面的方法

例如:

import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

public class GitHubCustomOauth2User implements OAuth2User {

  private OAuth2User user;

  public GitHubCustomOauth2User(OAuth2User user) {
    this.user = user;
  }

  @Override
  public Map<String, Object> getAttributes() {
    return user.getAttributes();
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return user.getAuthorities();
  }

  @Override
  public String getName() {
    return user.getAttribute("name");
  }

  public String getLogin() {
    return user.getAttribute("login");
  }

  public String getEmail() {
    return user.getAttribute("email");
  }
}

⚠️注意:对于对接不同的授权端是getName() 方法返回的值的名称发生变化

3.2 创建一个service 去继承 DefaultOAuth2UserService

里面实现加载授权用户数据的功能,根据不同授权端转变为不同的 OAuth2User
例如:

package com.openbayes.application.oauth;

import com.openbayes.domain.oauth.GitHubCustomOauth2User;
import com.openbayes.domain.oauth.SiomCustomOauth2User;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class CustomOauth2UserService extends DefaultOAuth2UserService {

  @Override
  public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    OAuth2AccessToken accessToken = userRequest.getAccessToken();
    String tokenValue = accessToken.getTokenValue();
    log.info("accessToken value: {}", tokenValue);

    ClientRegistration clientRegistration = userRequest.getClientRegistration();
    ClientRegistration.ProviderDetails providerDetails = clientRegistration.getProviderDetails();
    log.info("clientRegistration 为:{}", clientRegistration);
    log.info("clientRegistration providerDetails 为:{}", providerDetails);

    String attributeName =
        userRequest
            .getClientRegistration()
            .getProviderDetails()
            .getUserInfoEndpoint()
            .getUserNameAttributeName();
    log.info("attributeName 为:{}", attributeName);

    Map<String, Object> additionalParameters = userRequest.getAdditionalParameters();
    log.info("additionalParameters 为:{}", additionalParameters);

    OAuth2User oAuth2User = super.loadUser(userRequest);
    log.info("User information response: {}", oAuth2User.getAttributes());

    if (clientRegistration.getRegistrationId().equals("github")) {
      GitHubCustomOauth2User gitHubCustomOauth2User = new GitHubCustomOauth2User(oAuth2User);
      log.info("gitHubCustomOauth2User 的 Attributes 为:{}", gitHubCustomOauth2User.getAttributes());
      return gitHubCustomOauth2User;
    } else {
      xxxCustomOauth2User xxCustomOauth2User = new xxCustomOauth2User(oAuth2User);
      log.info("xxxCustomOauth2User 的 Attributes 为:{}", xxxxCustomOauth2User.getAttributes());
      return siomCustomOauth2User;
    }
  }
}

4、Configure Spring Security for OAuth2 Login

配置 OAuth Login 的配置
例如

   @Autowired private OAuth2AuthorizedClientService authorizedClientService;
    @Autowired private OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository;
    @Autowired private CustomOauth2UserService customOauth2UserService;
    @Autowired private Oauth2LoginSuccessHandler oauth2LoginSuccessHandler;
    @Autowired private Oauth2LoginFailedHandler oauth2LoginFailedHandler;
    @Autowired private Oauth2LoginOutSuccessHandler oauth2LoginOutSuccessHandler;

   http.oauth2Login()
              .loginPage("/login")
              .authorizedClientService(authorizedClientService)
              .authorizedClientRepository(oAuth2AuthorizedClientRepository)
              .userInfoEndpoint().userService(customOauth2UserService)
              .and()
              .successHandler(oauth2LoginSuccessHandler)
              .failureHandler(oauth2LoginFailedHandler);

这里面的配置包含了 OAuth 集群下的一些配置(下面讲到),加载用户信息的服务为刚才我们定义的 Service ,以及用户登录成功 和登录失败的 handler

5、extends SimpleUrlAuthenticationSuccessHandler

这个里面主要用来处理授权成功之后的逻辑,是注册用户还是直接登录等业务逻辑

一张图来解释

在这里插入图片描述
例子

@Component
@Slf4j
public class Oauth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

  @Autowired private CustomOauth2UserService customOauth2UserService;

  @Autowired private UserRepository userRepository;

  @Autowired private UserApplicationService userApplicationService;

  @Autowired private TokenService tokenService;

  @Override
  public void onAuthenticationSuccess(
      HttpServletRequest request, HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {

    if (authentication instanceof OAuth2AuthenticationToken) {
      OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication;
      String authorizedProvider = oauth2Token.getAuthorizedClientRegistrationId();
      log.info("authorizedProvider为{}", authorizedProvider);
      String redirectUrl = null;

      if (authorizedProvider.equals("github")) {

        GitHubCustomOauth2User oauth2User = (GitHubCustomOauth2User) authentication.getPrincipal();
        
          是注册呢?还是直接返回 token呢? 根据相关业务逻辑来判断
       
      } else if (authorizedProvider.equals("siom")) {
        SiomCustomOauth2User siomCustomOauth2User =
            (SiomCustomOauth2User) authentication.getPrincipal();

            和上面同理
        
    }
  }
}

6、extends SimpleUrlAuthenticationFailureHandler

这里主要为了登录失败的情况,主要做一些打印日志,以及给前端返回错误信息。这样前端可以根据返回的结果判断授权登录是成功还是失败,做不一样的操作以及跳转到不同的页面

到这里, 在单节点上使用 Github 登录就已经结束了

OAuth Logout

其实还可以引入登出的逻辑,登出的 handler 里面就是清理 session 的工作

  • 首先要进行 logout 的配置
    例如
 http.logout()
              .logoutUrl("/auth/logout")
              .logoutSuccessHandler(oauth2LoginOutSuccessHandler)
              .invalidateHttpSession(true)
              .clearAuthentication(true)
              .deleteCookies("JSESSIONID");

上面的配置就是,等你调用接口 /auth/logout 登出的时候,会对seeion 以及 cookie进行处理,oauth2LoginOutSuccessHandler 也会做相应的操作(打印日志,返回信息给前端)
例如

@Component
@Slf4j
public class Oauth2LoginOutSuccessHandler extends SimpleUrlLogoutSuccessHandler {

  @Override
  public void onLogoutSuccess(
      HttpServletRequest request, HttpServletResponse response, Authentication authentication)
      throws IOException, ServletException {
    HttpSession session = request.getSession(false);
    if (session != null) {
      session.invalidate();
    }

    log.info("User logged out at {}", new Date());
    response.setStatus(HttpStatus.UNAUTHORIZED.value());
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.getWriter().write("{\"message\": \"Logout successful\"}");
    response.getWriter().flush();
  }
}

对接其他作为Client登录

如果我们还想引入其他第三方的登录,和上面的流程一下的。 我们这次增加一个和框架没有集成的第三方(像github和谷歌等因为集成的很好,我们的配置就会很好,流程也会比较顺利)

1、首先就是client 的配置

security:
    oauth2:
      client:
        registration:
          github:
            clientId: 56ca77ae71cxxxxxx
            clientSecret: e1aa08298c5d0f5f9c35414355666b8xxxxxxxxx
            scope:
              - user:email
              - read:user
          aaaa:
            client-id: 6b91bc5axxxxxx
            client-secret: 97eabe644084f6442a58bxxxxxx
            authorization-grant-type: authorization_code
            redirect-uri: "http://xxxxxxx/api/login/oauth2/code/aaaa"
            client-name: SIOM
        provider:
          aaaa:
            authorization-uri: xxxxxxx
            token-uri: xxxxxx
            user-info-uri: xxxxxxx
            user-name-attribute: id

这里的配置就是看官方文档进行配置。学习网址:OAuth2

2、创建新的实体类去实现 OAuth2User

例如



import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

public class xxxxCustomOauth2User implements OAuth2User {

  private OAuth2User user;

  public xxxxCustomOauth2User(OAuth2User user) {
    this.user = user;
  }

  @Override
  public Map<String, Object> getAttributes() {
    return user.getAttributes();
  }

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return user.getAuthorities();
  }

  @Override
  public String getName() {
    return user.getAttribute("id");
  }

  public String getAccountNo() {
    return (String)
        ((Map<String, Object>) user.getAttributes().get("attributes")).get("account_no");
  }

  public String getUserId() {
    return (String) ((Map<String, Object>) user.getAttributes().get("attributes")).get("user_uid");
  }

  public String getEmail() {
    return (String) ((Map<String, Object>) user.getAttributes().get("attributes")).get("email");
  }

  public String getMobilePhone() {
    return (String) ((Map<String, Object>) user.getAttributes().get("attributes")).get("mobile");
  }
}

⚠️注意:这里重写的 getName() 就是根据返回的授权用户的 id

3、在 extends DefaultOAuth2UserService 的Service 中增加判断,授权用户最终转变为那个授权实体类进行返回

上面的例子上已经有所展示

4、在 loginSuccessHandler 里面增加判断是那个 AuthorizedClientRegistrationId ,进行不同的登录或者创建用户操作

在这里单节点下两个不同的第三方的 OAuth Client 的整体流程就已经结束了,但是在集群环境下就会出现新的问题

1、 OAuth Client 集群环境下的会话丢失问题

解决方式

关于 JDBC Session的配置

1、引入 spring-session-jdbc 进行会话存储
application.yml

 spring:
  session:
    store-type: jdbc
    jdbc:
      initialize-schema: always

关于 OAuth Client 的配置

@Configuration
public class OAuth2AuthorizedClientConfig {

  @Bean
  public OAuth2AuthorizedClientRepository authorizedClientRepository() {
    return new HttpSessionOAuth2AuthorizedClientRepository();
  }

  @Bean
  public OAuth2AuthorizedClientService authorizedClientService(
      JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository) {
    return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository);
  }
}

WebSecurity

   http.oauth2Login()
              .loginPage("/login")
              .authorizedClientService(authorizedClientService)
              .authorizedClientRepository(oAuth2AuthorizedClientRepository)
              .userInfoEndpoint().userService(customOauth2UserService)
              .and()
              .successHandler(oauth2LoginSuccessHandler)
              .failureHandler(oauth2LoginFailedHandler);

OAuth 2.0 Client supports application clustering

上面链接的摘录
在这里插入图片描述
在这里插入图片描述

2、authorization_request_not_fund

情况:在第一次成功登录后,客户端和授权端都登出,客户端再发起第三方登录的请求会出现这个问题。(不一定,需要看授权端的登出操作是否彻底请求用户的session及cookie)

无关集群环境,只是纯属工作中的坑。

解决:授权服务器在登出的时候要彻底清除用户的seesion 和 cookie信息

到此这个需求终于结束🎉🎉,踩的坑不少

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深入解析K8S专栏介绍】

序言 时间永远是旁观者&#xff0c;所有的过程和结果&#xff0c;都需要我们自己去承担。 Kubernetes (k8s) 是一个容器编排平台&#xff0c;允许在容器中运行应用程序和服务。 专栏介绍 欢迎订阅&#xff1a;【深入解析k8s】专栏 简单介绍一下这个专栏要做的事&#xff1a; 主…

8年测试老兵竟被面试官10分钟pass,这也太难了吧...

前言 随着软件测试领域对于技术要求越来越清晰&#xff0c;到现在测试人员在市场上的岗位需求也变得越来越复杂。极大部分的企业都开始对自动化测试岗位有了更多的需要。自然而然&#xff0c;面试就相对于非常重要了。 笔试部分 1.阐述软件生命周期都有哪些阶段&#xff1f;…

stm32cubemx IAP升级(二)

stm32cubemx IAP升级- App的制作 板卡&#xff1a;Nucleo-L412 平台&#xff1a;macbook pro 工具&#xff1a;vscode stm32cubemx stm32cubeProgramer cmake toolchain 整体思路 将App设置为从0x08007000地址启动&#xff0c;然后初始化一路串口用作接收上位机的升级数据&a…

docker容器:docker镜像的三种创建方法及dockerfile案例

目录 一、基于现有镜像创建 1、创建启动镜像 2、生成新镜像 二、基于本地模板创建 1、OPENVZ 下载模板 2、导入容器生成镜像 三、基于dockerfile创建 1、dockerfile结构及分层 2、联合文件系统 3、docker镜像加载原理 4、dockerfile操作常用的指令 (1)FROM指令 (…

kotlin协程flow retry retryWhen(2)

kotlin协程flow retry retryWhen&#xff08;2&#xff09; 一、retry import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlockingfun main(args: Array<String>) {runBlocking {(1..5).asFlow().onEach {if (it 3) {println("-")throw Runti…

入行IC选择国企、私企还是外企?(内附各IC大厂薪资福利情况)

不少人想要转行IC&#xff0c;但不知道该如何选择公司&#xff1f;下面就来为大家盘点一下IC大厂的薪资和工作情况&#xff0c;欢迎大家在评论区补充。 一&#xff0e;老 牌 巨 头 在 IC 设计领域深耕许久&#xff0c;流程完善、技术扎实&#xff0c;公司各项制度都很完善、前…

关于改造维护工单BAPI_ALM_ORDER_MAINTAIN用于生产订单组件批量修改

1、研究背景 1.1、业务背景 由于销售、研发、工艺等需要频繁变更&#xff0c;导致工单中组件需要频繁的进行变更&#xff0c;修改组件的物料&#xff0c;数量&#xff0c;库存地点&#xff0c;工序等内容。 1.2、技术痛点 为了满足要求&#xff0c;使用了函数&#xff1a;CO…

FVM链的Themis Pro(0x,f4) 5日IDO超百万美元,或让Filecoin逆风翻盘

交易一直是DeFi乃至web3领域最经久不衰的话题&#xff0c;也因此催生了众多优秀的去中心化协议&#xff0c;如Uniswap和Curve。这些协议逐渐成为了整个系统的基石。 在永续合约方面&#xff0c;DYDX的出现将WEB2时代的订单簿带回了web3。其链下交易的设计&#xff0c;仿佛回到了…

为你的软件测试全职业生涯规划保驾护航

目录 前言 1. 软件测试行业现状和未来趋势 2. 从初级测试工程师到高级测试架构师的职业路径 3. 如何提升自身技能和素质 4. 如何管理好自己的职业生涯 总结 前言 作为一名软件测试人员&#xff0c;职业生涯规划是非常重要的。在这篇文章中&#xff0c;我将从以下几个方面…

Mac电脑安装apktool工具包

开发中少不了想看看别人怎么实现某个功能&#xff0c;于是会用到apktool反编译apk&#xff0c;apktool工具包是一个压缩包&#xff0c;下载后无需安装&#xff0c;简单配置下执行命令就能反编译apk了&#xff0c;下面我们看一下如何在Mac OS系统下如何安装apktool工具包&#x…

《Netty》从零开始学netty源码(四十)之SizeClasses

目录 SizeClasses SizeClasses 在netty中&#xff0c;内存会被切割成不同size的块&#xff0c;在分配的时候会根据所需的大小分配相应的内存大小&#xff0c;然而并不是所有的大小都会有相应大小的内存块&#xff0c;比如想要11kb的内存&#xff0c;它并不会确切的给你11kb&am…

Mybatis高级映射及延迟加载

准备数据库表&#xff1a;一个班级对应多个学生。班级表&#xff1a;t_clazz&#xff1b;学生表&#xff1a;t_student 创建pojo&#xff1a;Student、Clazz // Student public class Student {private Integer sid;private String sname;//...... }// Clazz public class Cla…

我开发了一个温柔的智能客服聊天机器人ChatBot,并回答为什么不是ChatGPT(附思路和代码)

前言 若问2023年科技领域什么最火&#xff0c;那当然是ChatGPT了&#xff0c;这么智能的对话机器人&#xff0c;给人带来无限的想象&#xff0c;围绕着ChatpGPT的各种热点和创意层出不穷。作为一个多年从事编程开发的程序员&#xff0c;我对于这么大的一个热点也很兴奋&#x…

视频虚拟主播怎们搞?体验报告全记录;一图掌握SD应用精髓;Chat效率工具大汇总;品牌营销进入AI时代 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『Stable Diffusion界面解读』一张图掌握SD使用精髓 ShowMeAI资源编号&#xff1a;No.R052 图片可能被平台压缩&#xff0c;加入知识星…

安装配置 JupyterLab ubuntu20.04

目录 ​编辑 &#xff08;1&#xff09;安装 &#xff08;2&#xff09;配置 &#xff08;1&#xff09;生成配置文件 &#xff08;2&#xff09;生成jupyterlab的登录密码 &#xff08;3&#xff09;修改 jupyter 的配置文件 &#xff08;4&#xff09;安装 jupyterlab…

Mybatis(三)

1、mybatis中的连接池以及事务控制 原理部分了解&#xff0c;应用部分会用 mybatis中连接池使用及分析 mybatis事务控制的分析2、mybatis基于XML配置的动态SQL语句使用 会用即可 mappers配置文件中的几个标签&#xff1a; <if> …

Linux网络编程 第八天

目录 学习目标 内容回顾 完善网页服务器 中文乱码问题 服务器中断处理 读取目录文件 BS模式示意图 Web服务器开发流程图 日志服务器 Libevent下的网页服务器 学习目标 第八天主要是在第七天的基础上&#xff0c;完善网页服务器的设计&#xff0c;学习日志服务器以及li…

MySQL中distinct和group by性能比较

distinc的使用 用法 select distinct columns from table_name where where_conditions;示例&#xff1a; DISTINCT 用于返回唯一不同的值&#xff08;即去重后的值&#xff09; &#xff0c;使用时需要放在查询语句中第一个查询字段前使用。如果列有NULL值&#xff0c;会将所…

C语言/C++随机数生成,程序运行时间计时器(含高精度计时器),包括Windows环境与Linux环境

&#x1f38a;【数据结构与算法】专题正在持续更新中&#xff0c;各种数据结构的创建原理与运用✨&#xff0c;经典算法的解析✨都在这儿&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 -…

工厂模式概述

通常有三种形态: 简单工厂模式&#xff0c;不属于23种设计模式之一 工厂方法模式&#xff0c;是23种设计模式之一 抽象工厂模式&#xff0c;是23种设计模式之一 1.简单工厂模式是工厂模式的一种特殊实现&#xff0c;又被称为静态工厂方法模式 2.简单工厂模式解决的问题:客户端不…