三方登录的方式想必大家都很熟悉,基本健全的网页都会整几个入口,比如日常的微信、QQ,金融的支付宝,音视频的抖音、快手,码农领域的Github、Gitee等。
作为功能测试,我们就随机取一个简单的三方授权Gitee来尝试下。由于最近ChhatGPT写代码也是很火,这次的测试主要是chatGPT主导,本人负责辅助实现和测试,不经感叹效率高、能力强!
一、三方登录常规的原理和流程
-
用户点击第三方登录按钮,跳转到第三方平台的授权页面。
-
用户在第三方平台的授权页面上登录,确认授权。跳转到本地的服务,组装参数配置(response_type/client_id/redirect_uri/state,不同三方参数名可能不同,性质类似),请求三方平台。
-
第三方平台验证用户身份,生成授权码或访问令牌(state-对应提交过去的唯一值,code/auth_code,不同三方参数名可能不同,性质类似)。
-
用户的浏览器重定向回原网站(根据提交的redirect_uri进行回调),并将授权码或访问令牌传递给原网站。
-
原网站使用授权码或访问令牌向第三方平台请求用户信息(用户ID、头像、昵称等)。
-
第三方平台返回用户信息给原网站。
-
原网站使用用户信息创建或更新本地用户账号,完成用户登录。
了解了基本思路之后,java开发的角度,肯定上来先找轮子(如果有要求要自己实现的话,也可以参考轮子的代码)
这边简单测试,选用了Spring Boot+ JustAuth + Thymeleaf (+ Spring Security 完全没有登录逻辑,可以不加) + IDEA + 内网穿透
以上组件,基本都比较熟悉,简单聊一下 JustAuth
二、关于JustAuth
JustAuth 是一个开源的 Java 三方登录集成工具,它可以帮助开发者快速集成第三方登录功能。
目前支持超过 20 种第三方平台的登录认证,包括但不限于:GitHub、Gitee、微信、QQ、微博、Google、Facebook、Twitter、LinkedIn 等。
使用 JustAuth 可以避免开发者需要针对每个第三方平台编写繁琐的登录认证代码,从而提高开发效率。
从2018年11月开始,一经发布就受到大量的支持,截止目前经过一个个开源人的伟大奉献,到2023年3月10日,JustAuth项目在Github已经有14.5K的star数,足以窥见它的成绩。
下面就借这轮子,一起学习了解下本地测试三方登录的实操流程。前后流程会比较长,请耐心看完。
三、搭建全流程
3.1 前期准备:内网穿透
关于本地开发工具IDEA这就不用多说了,关于使用其他开发工具都是可以的。
由于我们要本地对接公网做联调测试,所以需要内网穿透或者其他自己配置代理的方式都行。
我这边是之前使用购买的花生壳一个6块的域名,并且有送2个内网穿透的配置体验机会,所以就选择这个,这个产品做的也是比较好用。但是HTTP【为响应《网络安全法》,将于2022年6月9日开始,为您提供更安全的外网访问服务,暂停使用HTTP映射】。但是依旧可以配置HTTPS的访问方式,映射本地HTTP的端口都是可以的。
具体配置方式参考:https://service.oray.com/question/8156.html
我参照以上指南,是可以配置好一个公网可访问,代理本地127.0.0.1:8080的接口。
已经具备了以上条件后,继续往下走。
3.2 前期准备:申请一个三方登录授权应用
需要在你需要三方登录对应的平台上去申请三方登录的权限,这边拿Gitee举例。
Gitee官方OAuth的文档:https://gitee.com/api/v5/oauth_doc#/
其中包含:应用名称(随意)、应用描述(随意)、应用主页(随意)、应用回调地址(这是主要的,你自己服务可以回调的地址)、权限(按需选择)
填写好之后直接提交配置,就可以获取接口所需的client-id和client-secret。
- 在 修改资料 -> 第三方应用,创建要接入码云的应用。
- 填写应用相关信息,勾选应用所需要的权限。其中: 回调地址是用户授权后,码云回调到应用,并且回传授权码的地址。正常三方登录,勾选用户权限+个人邮箱信息基本就够了。
- 创建成功后,会生成 Cliend ID 和 Client Secret。他们将会在上述OAuth2 认证基本流程用到。
3.3 开发指南:引入组件
确定具备一个基本的java\maven的项目,在pom.xml中引入
- Spring Boot
- JustAuth
- Thymeleaf
- Spring Security 不需要其他认证,可以不加
这边为了测试偷懒,引入了【justauth-spring-boot-starter】,以下是测试的POM
<dependencies>
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 排除 Spring Boot 默认的日志依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Thymeleaf 模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Spring Security 安全框架 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
3.4 开发指南:构建一个基本的授权登录框架
以下含非必须环节,不需要可自动过滤。
SpringSecurity身份验证,内置一个账号密码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/home").authenticated()
.antMatchers("/oauth/**").permitAll()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login?error=true")
.and()
.logout()
.logoutSuccessUrl("/login");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER")
.and()
.withUser("admin").password("{noop}123456").roles("ADMIN");
}
}
简单配备一个日志输出:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
简单配备一个登录页:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<div th:if="${param.error}">
<p>Invalid username and password.</p>
</div>
<div th:if="${param.logout}">
<p>You have been logged out.</p>
</div>
<form th:action="@{/login}" method="post">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username"/>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password"/>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
<div>
<a th:href="@{/oauth/login/gitee}">
<img src="https://huashuimoyu.com/image/thirdlogo/gitee.png" width="40px" height="40px"/>
</a>
</div>
</body>
</html>
简单配备一个登录跳转逻辑:
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home";
}
@GetMapping("/home")
public String home2() {
return "home";
}
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
public String loginWithUsenamePassword(String username,String password){
System.out.println(username);
System.out.println(password);
return "success";
}
}
到这边,使用Springboot+SpringSecurity 依靠登录框进行账号密码登录的基本流程可以通了,此外依赖花生壳一个映射的配置,使用公网一个https的访问路径就可以访问到这个登录页了。
3.5 JustAuth+Gitee的三方登录
下面接入JustAuth补充Gitee的三方登录
前面依赖已经引入了,使用自动装配,只要在application的配置文件配置下、书写确定的跳转和回调接口即可
justauth:
enabled: true
type:
QQ:
GITEE:
client-id: 56ba1xxxxxx3c7d
client-secret: 90c7xxxxxx129a97
redirect-uri: https://exxxxxxxxn/oauth/gitee/callback #就你本地自己提供出去的登录跳转的API,注意是公网可访问的
使用JustAuth简单实现一个调用和回调:
刚才在登录页就简单用一张图片链接触发一个三方登录请求。
<div>
<a th:href="@{/oauth/login/gitee}">
<img src="https://huashuimoyu.com/image/thirdlogo/gitee.png" width="40px" height="40px"/>
</a>
</div>
点击图片后,跳转到后端接口,后端接口组装认证参数,提交到Gitee接口。
@GetMapping("/login/{type}")
public void login(@PathVariable String type, HttpServletResponse response) throws IOException {
AuthRequest authRequest = factory.get(type);
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
请求Gitee,跳转到Gitee的授权登录页,选择想要授予的权限,点击确定。
确定后,Gitee就会将网址跳转到我们提交的redirect_url上,我们做出正确响应即可。
@RequestMapping("/{type}/callback")
public AuthResponse login(@PathVariable String type, AuthCallback callback) {
AuthRequest authRequest = factory.get(type);
AuthResponse response = authRequest.login(callback);
System.out.println("【response】= {}"+JSON.toJSONString(response));
return response;
}
接口获取授权信息:
这个时候,我们的页面可以回到一个授权成功,正在登录页。我们可以在这个时候拿到Gitee用户的一些昵称、头像、uid这些信息,记录在自己网站中,后期可以直接认证。