Java实现第三方登录

前言

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

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

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

OAuth2 认证基本流程(Gitee)

openapi-oauth2-flow

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

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

1.1 创建工程项目

image-20240314142559166

1.2 引入依赖

    <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

@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

@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

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

UserInfo.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

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 基础配置

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后点击头像点击 设置 -> 第三方应用,创建要接入码云的应用,填写基本的信息

image-20240314151340636

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

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

image-20240314151538221

三、核心代码实现

3.1 配置文件

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

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

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

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

四、接口测试

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

https://gitee.com/oauth/authorize?client_id=&redirect_uri=http://localhost:8080/gitee&response_type=code

注意替换client_id和redirect_uri

测试

image-20240315114128483

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

在这里插入图片描述

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

image-20240315114343299

参考资料

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笑的像个child

好人一生平安,先磕为敬

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值
>