Java实现三方登录

前言

第三方登录功能是指用户可以使用其他平台(如QQ、微信、微博等)的账号来登录您的应用或网站,而无需单独注册新账号。这种功能可以提高用户体验,减少用户的注册和登录步骤,同时也可以增加用户的信任度。

实现第三方登录功能通常需要以下步骤:

  1. 选择第三方登录提供商:您需要选择要集成的第三方登录提供商,比如Google、Facebook、GitHub、Twitter等。每个提供商都有自己的开发文档和接入方式。
  2. 创建应用并获取API密钥:在所选第三方平台上创建一个应用,并获取相应的API密钥、密钥和其他必要的信息。这些信息将用于在您的应用中进行认证和授权。
  3. 集成第三方登录SDK:根据所选提供商的文档,将其提供的SDK集成到您的应用中。SDK通常提供了登录按钮、授权流程和回调函数等功能。
  4. 处理用户授权和认证:当用户点击第三方登录按钮时,您的应用将引导用户到第三方平台进行授权。一旦用户授权成功,第三方平台将返回一个授权码或令牌给您的应用。
  5. 获取用户信息:使用授权码或令牌,您的应用可以向第三方平台请求用户的基本信息,如姓名、邮箱地址等。这些信息可以用于创建用户账号或进行个性化设置。
  6. 处理用户会话:一旦用户成功登录,您的应用应该创建一个会话,以便在用户访问其他页面时保持登录状态。您还可以将第三方登录与本地账号进行关联,以便用户可以选择不同方式登录。
  7. 安全性考虑:确保在实现第三方登录功能时,要考虑到安全性问题,比如保护用户信息、防止CSRF攻击、使用HTTPS等。

OAuth2 认证基本流程(Gitee)

一、创建工程项目以及前期准备

本文将会以Gitee为例子,其余QQ和微信同理

1.1 创建工程项目

1.2 引入依赖

xml 复制代码
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/>
    </parent>

	<dependencies>
        <!--spring boot starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!--spring boot web starter-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--JPA-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--h2数据库-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--spring状态机-->
        <dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-core</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

        <!--httpClient提供http访问-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.6</version>
        </dependency>

        <!--fastjson 进行json与对象之间的转换-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>
    </dependencies>

1.3 基础代码实现

UserController.java

java 复制代码
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @PostMapping("/login")
    public String login(String account, String password) {
        return userService.login(account, password);
    }

    @PostMapping("/register")
    public String register(@RequestBody UserInfo userInfo) {
        return userService.register(userInfo);
    }

}

UserService.java

java 复制代码
@Service
public class UserService {
    @Autowired
    UserRepository userRepository;

    public String login(String account, String password) {
        UserInfo userInfo = userRepository.findByUserNameAndUserPassword(account, password);
        if (userInfo == null) {
            return "account / password ERROR!";
        }
        return "Login Success";
    }

    public String register(UserInfo userInfo) {
        if (checkUserExists(userInfo.getUserName())) {
            throw new RuntimeException("User already registered.");
        }
        userInfo.setCreateDate(new Date());
        userRepository.save(userInfo);
        return "Register Success!";
    }

    public boolean checkUserExists(String userName) {
        UserInfo user = userRepository.findByUserName(userName);
        return user != null;
    }
}

UserRepository.java

java 复制代码
@Repository
public interface UserRepository extends JpaRepository<UserInfo, Integer> {
    //根据用户 userName查询信息
    UserInfo findByUserName(String userName);
    //根据用 户名称 和 密码 查询用户信息
    UserInfo findByUserNameAndUserPassword(String account, String password);
}

UserInfo.java

java 复制代码
@Data
@Entity
@Table(name = "user_info")
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(nullable = false)
    private String userName;

    @Column(nullable = false)
    private String userPassword;

    @Column(nullable = false)
    private Date createDate;

    @Column
    private String userEmail;
}

HttpClientUtils.java

java 复制代码
package com.thirdPartyLogin.utils;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpMethod;
import java.io.IOException;

public class HttpClientUtils {
    public static JSONObject execute(String url, HttpMethod httpMethod) {
        HttpRequestBase http = null;
        try {
            HttpClient client = HttpClients.createDefault();
            //根据 HttpMethod 进行 HttpRequest的创建
            if(httpMethod == HttpMethod.GET) {
                http = new HttpGet(url);
            } else {
                http = new HttpPost(url);
            }
            HttpEntity entity = client.execute(http).getEntity();
            return JSONObject.parseObject(EntityUtils.toString(entity));
        } catch (IOException e) {
            throw new RuntimeException("Request failed. url = " + url);
        } finally {
            assert http != null;
            http.releaseConnection();
        }
    }
}

文章着重在项目中的功能实现上,可能有部分代码存在不规范性(比如未统一响应类型等),还请见谅。

1.4 基础配置

properties 复制代码
server.port=8080
spring.datasource.url=jdbc:h2:mem:design
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=root
spring.datasource.password=toor
spring.h2.console.enabled=true
spring.h2.console.path=/myh2
spring.jpa.show_sql=true
spring.jpa.properties.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.hbm2ddl.auto=update

二、接入Gitee

登录Gitee后点击头像点击 设置 -> 第三方应用,创建要接入码云的应用,填写基本的信息

创建应用后会有Client IDClient Secret,Client ID是Gitee为每个请求授权的个人或企业提供的唯一ID标识,

Client Secret是第三方平台授权密码

三、核心代码实现

3.1 配置文件

本文使用H2数据库主要是快速实现功能,具体的可以更换为MySQL等

properties 复制代码
server.port=8080
spring.datasource.url=jdbc:h2:mem:design
spring.datasource.driver-class-name=org.h2.Driver
#自定义的H2的数据库控制台的账户密码
spring.datasource.username=root
spring.datasource.password=toor
spring.h2.console.enabled=true
#进入H2数据库的路径
spring.h2.console.path=/myh2
spring.jpa.show_sql=true
spring.jpa.properties.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.hbm2ddl.auto=update
#申请的client_id、client_secret填写在这儿
gitee.client_id=****
gitee.client_secret=****
#回调地址
gitee.callback=http://localhost:8080/gitee
#与前端协定的state的值,本次没有用到
gitee.state=GITEE
#回调后的授权接口
gitee.token.url=https://gitee.com/oauth/token?grant_type=authorization_code&client_id=?&client_secret=?&redirect_uri=?&code=
gitee.user.url=https://gitee.com/api/v5/user?access_token=
#Gitee用户登陆时,进行自动注册时添加的用户前缀
gitee.user.prefix=${gitee.state}@

3.2 核心代码实现

Login3rdTarget.java

java 复制代码
package com.thirdPartyLogin.adapter;

public interface Login3rdTarget {
    public String loginByGitee(String code, String state);
    public String loginByWechat(String ... params);
    public String loginByQQ(String ... params);
}

Login3rdAdapter.java

java 复制代码
package com.thirdPartyLogin.adapter;

import com.alibaba.fastjson.JSONObject;
import com.thirdPartyLogin.pojo.UserInfo;
import com.thirdPartyLogin.service.UserService;
import com.thirdPartyLogin.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Author YZK
 * @Date 2024/3/14
 * @Desc
 */
@Component
public class Login3rdAdapter extends UserService implements Login3rdTarget {
    @Value("${gitee.state}")
    private String giteeState;

    @Value("${gitee.token.url}")
    private String giteeTokenUrl;

    @Value("${gitee.user.url}")
    private String giteeUserUrl;

    @Value("${gitee.user.prefix}")
    private String giteeUserPrefix;

    @Value("${gitee.client_id}")
    private String giteeClient_id;
    @Value("${gitee.client_secret}")
    private String giteeClient_secret;
    @Value("${gitee.callback}")
    private String giteeCallBack;

    @Override
    public String loginByGitee(String code, String state) {
        //进行state判断,state的值是前端与后端商定好的,前端将State传给Gitee平台,Gitee平台回传state给回调接口
//        if (!giteeState.equals(state)) {
//            throw new UnsupportedOperationException("Invalid state!");
//        }

        //请求Gitee平台获取token,并携带code
        String replace = giteeTokenUrl
                .replace("client_id=?", "client_id=" + giteeClient_id)
                .replace("client_secret=?", "client_secret=" + giteeClient_secret)
                .replace("redirect_uri=?", "redirect_uri=" + giteeCallBack);
        String tokenUrl = replace.concat(code);
        JSONObject tokenResponse = HttpClientUtils.execute(tokenUrl, HttpMethod.POST);
        String token = String.valueOf(tokenResponse.get("access_token"));
        System.out.println(token);
        //请求用户信息,并携带 token
        String userUrl = giteeUserUrl.concat(token);
        JSONObject userInfoResponse = HttpClientUtils.execute(userUrl, HttpMethod.GET);

        //获取用户信息,userName添加前缀 GITEE@, 密码保持与userName一致。
        String userName = giteeUserPrefix.concat(String.valueOf(userInfoResponse.get("name")));
        String password = userName;

        //"自动注册" 和登录功能,此处体现了方法的 "复用"
        return autoRegister3rdAndLogin(userName, password);
    }


    private String autoRegister3rdAndLogin(String userName, String password) {
        //如果第三方账号已经登录过,则直接登录
        if (super.checkUserExists(userName)) {
            return super.login(userName, password);
        }
        UserInfo userInfo = new UserInfo();
        userInfo.setUserName(userName);
        userInfo.setUserPassword(password);
        userInfo.setCreateDate(new Date());

        //如果第三方账号是第一次登录,先进行"自动注册"
        super.register(userInfo);
        //自动注册完成后,进行登录
        return super.login(userName, password);
    }

    @Override
    public String loginByWechat(String... params) {
        return null;
    }

    @Override
    public String loginByQQ(String... params) {
        return null;
    }
}

四、接口测试

因为本文没有写前端登录界面,所以只有在浏览器直接输入,这个链接应该是用户在登录时,点击第三方登录时进行跳转的。

gitee.com/oauth/autho...

注意替换client_id和redirect_uri

测试

点击授权后,就会进入回调地址http://localhost:8080/gitee

如果用户为首次登录,那就会自动的注册到数据库中

参考资料

贯穿设计模式 (豆瓣) (douban.com)

相关推荐
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
LunarCod1 小时前
WorkFlow源码剖析——Communicator之TCPServer(中)
后端·workflow·c/c++·网络框架·源码剖析·高性能高并发
码农派大星。2 小时前
Spring Boot 配置文件
java·spring boot·后端
杜杜的man2 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*2 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
llllinuuu2 小时前
Go语言结构体、方法与接口
开发语言·后端·golang
cookies_s_s2 小时前
Golang--协程和管道
开发语言·后端·golang
为什么这亚子2 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
想进大厂的小王3 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea