完美整合第三方登录

118 阅读5分钟

JustAuth 开箱即用的整合第三方登录的开源组件

如何使用

下面是对gitee第三方登录的整合

查看 授权登录流程

官网 JustAuth

在gitee创建第三方应用

  1. 获取生成的 Client ID
  2. 获取生成的 Client Secret
  3. 设置回调地址 http://127.0.0.1/oauth/callback?source=gitee

表设计

CREATE TABLE `social_user`
(
    `id`                 bigint(20)   NOT NULL COMMENT '主键',
    `auth_id`            varchar(255) NOT NULL COMMENT '第三方用户来源+第三方系统的唯一ID',
    `source`             varchar(255) NOT NULL COMMENT '第三方用户来源',
    `uuid`               varchar(255) NOT NULL COMMENT '第三方系统的唯一ID',
    `user_name`          varchar(30)  NOT NULL COMMENT '登录账号',
    `nick_name`          varchar(30)  DEFAULT '' COMMENT '用户昵称',
    `email`              varchar(255) DEFAULT '' COMMENT '用户邮箱',
    `avatar`             varchar(500) DEFAULT '' COMMENT '头像地址',
    `access_token`       varchar(255) NOT NULL COMMENT '用户的授权令牌',
    `expire_in`          int(11)      DEFAULT NULL COMMENT '用户的授权令牌的有效期,部分平台可能没有',
    `refresh_token`      varchar(255) DEFAULT NULL COMMENT '刷新令牌,部分平台可能没有',
    `open_id`            varchar(255) DEFAULT NULL COMMENT '第三方用户的 open id,部分平台可能没有',
    `access_code`        varchar(255) DEFAULT NULL COMMENT '平台的授权信息,部分平台可能没有',
    `union_id`           varchar(255) DEFAULT NULL COMMENT '第三方用户的 union id,部分平台可能没有',
    `scope`              varchar(255) DEFAULT NULL COMMENT '第三方用户授予的权限,部分平台可能没有',
    `token_type`         varchar(255) DEFAULT NULL COMMENT '个别平台的授权信息,部分平台可能没有',
    `id_token`           varchar(255) DEFAULT NULL COMMENT 'id token,部分平台可能没有',
    `mac_algorithm`      varchar(255) DEFAULT NULL COMMENT '小米平台用户的附带属性,部分平台可能没有',
    `mac_key`            varchar(255) DEFAULT NULL COMMENT '小米平台用户的附带属性,部分平台可能没有',
    `code`               varchar(255) DEFAULT NULL COMMENT '用户的授权code,部分平台可能没有',
    `oauth_token`        varchar(255) DEFAULT NULL COMMENT 'Twitter平台用户的附带属性,部分平台可能没有',
    `oauth_token_secret` varchar(255) DEFAULT NULL COMMENT 'Twitter平台用户的附带属性,部分平台可能没有',
    `deleted`            char(1)      DEFAULT '0' COMMENT '删除标志',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB COMMENT ='社会化关系表';



CREATE TABLE `social_user_auth`
(
    `id`             bigint(20) NOT NULL COMMENT '主键',
    `user_id`        bigint(20) NOT NULL COMMENT '系统用户ID',
    `user_type`      tinyint(1) NOT NULL COMMENT '系统用户类型 1=管理后台用户、2=C端用户',
    `social_user_id` bigint(20) NOT NULL COMMENT '社会化用户ID',
    `deleted`        char(1) DEFAULT '0' COMMENT '删除标志',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB COMMENT ='社会化用户 & 系统用户关系表';

添加依赖

我使用已经对justauth进行springboot封装后的 starter

<dependency>
 <groupId>com.xkcoding.justauth</groupId>
 <artifactId>justauth-spring-boot-starter</artifactId>
 <version>1.4.0</version>
</dependency>

 <!--redis-->
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <exclusions>
   <exclusion>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
   </exclusion>
  </exclusions>
 </dependency>
 
 <!--jedis作为连接池-->
 <dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
 </dependency>

配置文件

server:
  port: 80
spring:
  application:
    name: jusauth-lab
  main:
    allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
    allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类

  redis:
    host: 192.168.1.242
    password: 123456
    port: 6379
    database: 0
    timeout: 6000
    jedis:
      pool:
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 150
        # 连接池中的最大空闲连接
        max-idle: 20
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1
        # 连接池中的最小空闲连接
        min-idle: 8


justauth:
  enabled: true
  type:
    GITEE:
      client-id: 296dde6c2*********3dfbbb7688c8484afb4488eb21d12e
      client-secret: cf59********366460d02f5f4b02b9ea83ce692dbd2392ed6c2fe7b61ca2b
      redirect-uri: http://127.0.0.1/oauth/callback?source=gitee
  cache:
    type: redis

访问层

/**
 * @author LGC
 */
@Slf4j
@RestController
@RequestMapping("/oauth")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class JustAuthController {

    private final AuthRequestFactory factory;

    /**
     * 第三方应用名称列表
     *
     * @return
     */
    @GetMapping("/list")
    public List<String> list() {
        return factory.oauthList();
    }


    /**
     * 生成授权地址
     * 前端来进行跳转,并设置回调地址
     *
     * @param source      来源
     * @param redirectUri 前端重定向地址
     * @throws IOException
     */
    @GetMapping("/social-auth-redirect")
    public String binding(@RequestParam("source") String source, @RequestParam("redirectUri") String redirectUri) throws IOException {
        AuthRequest authRequest = factory.get(source);
        // 生成跳转地址
        String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
        // 替换重定向地址
        UrlBuilder builder = UrlBuilder.of(authorizeUri, Charset.defaultCharset());
        String key = "redirect_uri";
        TableMap<CharSequence, CharSequence> query = (TableMap<CharSequence, CharSequence>)
                ReflectUtil.getFieldValue(builder.getQuery(), "query");
        query.remove(key);
        // 后添加
        builder.addQuery(key, redirectUri);
        return builder.build();
    }

    /**
     * 生成授权地址 
     * 后端进行跳转
     *
     * @param source   来源
     * @param response
     * @throws IOException
     */
    @GetMapping("/redirect/{source}")
    public void binding(@PathVariable String source, HttpServletResponse response) throws IOException {
        AuthRequest authRequest = factory.get(source);
        response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
    }


    /**
     * 由于没有前端,所以直接回调到后端
     * 一般都是由前端解析回调参数然后传递给后端校验
     *
     * @param source 来源
     * @param code   code
     * @param state  state
     * @return
     */
    @RequestMapping("/callback")
    public AuthResponse<AuthUser> callback(@RequestParam("source") String source,
                                           @RequestParam("code") String code,
                                           @RequestParam("state") String state) {
        if (StrUtil.isBlank(code)) {
            log.info("第三方用户拒绝授权,还可以更加其它参数进行判断,进行了缩减");
        } else {
            log.info("第三方用户同意授权,进行社交账号登录。。前端调用社交账号登录接口/socialLogin");
        }
        // 下面代码测试用
        AuthRequest authRequest = factory.get(source);
        AuthCallback callback = new AuthCallback();
        callback.setCode(code);
        callback.setState(state);
        AuthResponse<AuthUser> authResponse = authRequest.login(callback);
        log.info("authResponse:{}", JSONUtil.toJsonStr(authResponse));
        AuthUser authUser = authResponse.getData();
        AuthToken authToken = authUser.getToken();
        log.info("authUser信息:{}", JSONUtil.toJsonStr(authUser));
        log.info("authToken信息:{}", JSONUtil.toJsonStr(authToken));
        return authResponse;
    }

    /**
     * 社交账号登录
     * 第三方用户同意授权时调用
     *
     * @param source 来源
     * @param code   code
     * @param state  state
     * @return
     */
    @RequestMapping("/socialLogin")
    public Object socialLogin(@RequestParam("source") String source,
                              @RequestParam("code") String code,
                              @RequestParam("state") String state) {
        log.info("获取第三方授权认证信息");
        AuthRequest authRequest = factory.get(source);
        AuthCallback callback = new AuthCallback();
        callback.setCode(code);
        callback.setState(state);
        AuthResponse<AuthUser> authResponse = authRequest.login(callback);
        log.info("保存或更新第三方授权认证信息");

        log.info("判断是否绑定了系统账号,如果绑定了直接登录成功,没有绑定提示前端登录绑定");

        return "登录信息";
    }

    /**
     * 登录绑定
     * 假设使用用户密码登录绑定第三方用户
     *
     * @param username 用户
     * @param password 密码
     * @param source   来源
     * @return
     */
    @RequestMapping("/login")
    public Object login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        @RequestParam("source") String source,
                        @RequestParam("code") String code,
                        @RequestParam("state") String state) {
        log.info("账号密码登录");
        log.info("获取第三方授权认证信息");
        log.info("绑定系统用户,如果已绑定其它系统用户,解绑后在绑定");
        return "登录信息";
    }

}

测试

前端访问 http://127.0.0.1/oauth/redirect/gitee 地址回调到后端 /callback 接口