前言
第三方登录功能是指用户可以使用其他平台(如QQ、微信、微博等)的账号来登录您的应用或网站,而无需单独注册新账号。这种功能可以提高用户体验,减少用户的注册和登录步骤,同时也可以增加用户的信任度。
实现第三方登录功能通常需要以下步骤:
- 选择第三方登录提供商:您需要选择要集成的第三方登录提供商,比如Google、Facebook、GitHub、Twitter等。每个提供商都有自己的开发文档和接入方式。
- 创建应用并获取API密钥:在所选第三方平台上创建一个应用,并获取相应的API密钥、密钥和其他必要的信息。这些信息将用于在您的应用中进行认证和授权。
- 集成第三方登录SDK:根据所选提供商的文档,将其提供的SDK集成到您的应用中。SDK通常提供了登录按钮、授权流程和回调函数等功能。
- 处理用户授权和认证:当用户点击第三方登录按钮时,您的应用将引导用户到第三方平台进行授权。一旦用户授权成功,第三方平台将返回一个授权码或令牌给您的应用。
- 获取用户信息:使用授权码或令牌,您的应用可以向第三方平台请求用户的基本信息,如姓名、邮箱地址等。这些信息可以用于创建用户账号或进行个性化设置。
- 处理用户会话:一旦用户成功登录,您的应用应该创建一个会话,以便在用户访问其他页面时保持登录状态。您还可以将第三方登录与本地账号进行关联,以便用户可以选择不同方式登录。
- 安全性考虑:确保在实现第三方登录功能时,要考虑到安全性问题,比如保护用户信息、防止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 ID 和Client 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;
}
}
四、接口测试
因为本文没有写前端登录界面,所以只有在浏览器直接输入,这个链接应该是用户在登录时,点击第三方登录时进行跳转的。
注意替换client_id和redirect_uri
测试
点击授权后,就会进入回调地址http://localhost:8080/gitee
如果用户为首次登录,那就会自动的注册到数据库中