三方登录的方式想必大家都很熟悉,基本健全的网页都会整几个入口,比如日常的微信、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 是一个开源的 Java 三方登录集成工具,它可以帮助开发者快速集成第三方登录功能。
目前支持超过 20 种第三方平台的登录认证,包括但不限于:GitHub、Gitee、微信、QQ、微博、Google、Facebook、Twitter、LinkedIn 等。
使用 JustAuth 可以避免开发者需要针对每个第三方平台编写繁琐的登录认证代码,从而提高开发效率。
从2018年11月开始,一经发布就受到大量的支持,截止目前经过一个个开源人的伟大奉献,到2023年3月10日,JustAuth项目在Github已经有14.5K的star数,足以窥见它的成绩。
下面就借这轮子,一起学习了解下本地测试三方登录的实操流程。前后流程会比较长,请耐心看完。
关于本地开发工具IDEA这就不用多说了,关于使用其他开发工具都是可以的。
由于我们要本地对接公网做联调测试,所以需要内网穿透或者其他自己配置代理的方式都行。
我这边是之前使用购买的花生壳一个6块的域名,并且有送2个内网穿透的配置体验机会,所以就选择这个,这个产品做的也是比较好用。但是HTTP【为响应《网络安全法》,将于2022年6月9日开始,为您提供更安全的外网访问服务,暂停使用HTTP映射】。但是依旧可以配置HTTPS的访问方式,映射本地HTTP的端口都是可以的。
具体配置方式参考:https://service.oray.com/question/8156.html
我参照以上指南,是可以配置好一个公网可访问,代理本地127.0.0.1:8080的接口。
已经具备了以上条件后,继续往下走。
需要在你需要三方登录对应的平台上去申请三方登录的权限,这边拿Gitee举例。
Gitee官方OAuth的文档:https://gitee.com/api/v5/oauth_doc#/
其中包含:应用名称(随意)、应用描述(随意)、应用主页(随意)、应用回调地址(这是主要的,你自己服务可以回调的地址)、权限(按需选择)
填写好之后直接提交配置,就可以获取接口所需的client-id和client-secret。
确定具备一个基本的java\maven的项目,在pom.xml中引入
这边为了测试偷懒,引入了【justauth-spring-boot-starter】,以下是测试的POM
<dependencies>
<dependency>
<groupId>com.xkcoding.justauthgroupId>
<artifactId>justauth-spring-boot-starterartifactId>
<version>1.4.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigureartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
dependencies>
以下含非必须环节,不需要可自动过滤。
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");
}
}
简单配备一个日志输出:
<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>Logintitle>
head>
<body>
<h1>Loginh1>
<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">Loginbutton>
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的访问路径就可以访问到这个登录页了。
下面接入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这些信息,记录在自己网站中,后期可以直接认证。