1►第三方登录
当今社会,微信登录、QQ登录、抖音登录等等三方登录已经层出不穷,学会三方登录势在必行。
微信登录要认证开发者,必须为企业,个人不行,而且还要交300块钱。
QQ登录也要申请、微博登录也要申请。
还好Gitee给力,申请轻轻松松,谁都能轻松让Gitee作为第三方登录,此次我们就讲解Gitee来登录ry。其实其他的登录也是基本上一样的。
2►JustAuth 奥义·穿风刺
JustAuth能让我们第三方登录写少一些代码,它包装了国内外30多种三方登录。
学习JustAuth网站:
https://mp.weixin.qq.com/s?__biz=MzA3NDk3OTIwMg==&mid=2450633106&idx=1&sn=131e39d52347dffefbd4227b18b794bf&chksm=8892937fbfe51a69950cb0769e2b22d04217254b0e79cdcee4204aedb2007627ab6511b58355&token=29120304&lang=zh_CN#rd
https://justauth.wiki/guide/quickstart/how-to-use/#%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4
使用步骤
使用JustAuth总共分三步(这三步也适合于JustAuth支持的任何一个平台):
1、申请注册第三方平台的开发者账号。
我们找到gitee的设置,进入第三方应用,如下:
出来界面如下:
我现在是已经新建好了应用,大家是没有ruoyi-test。大家可以新建自己的应用:
应用主页随便填一个自己的应用页面即可。但是应用回调不能乱填,当我们gitee登录成功之后,gitee会自动跳转到应用回调地址,并且gitee会带上code,利用code可以得到所登录gitee用户信息。
2、创建第三方平台的应用,获取配置信息(accessKey, secretKey, redirectUri)。
上面我们已经创建了应用,自然有了这三个值。
3、使用该工具实现授权登陆。
利用工具先要引入依赖:
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.16.5</version>
</dependency>
依赖引入到核心框架(framework)下。
接下来改login.vue,如下:
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
...省略其他代码
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
<div style="width: 32px;height: 32px;margin-top: 5px;cursor: pointer;" title="利用Gitee登录" @click="giteeLogin">
<img style="height: 100%;width: 100%;" src="../assets/logo/gitee.png">
</div>
</el-form-item>
</el-form>
以上,我们添加的代码是:
有了该代码,页面呈现的样子是:
关于图片LOGO下载地址请去随便找一个,或者群文件找找。然后我们看一下点击事件:
giteeLogin() {
PreLoginByGitee().then(res => {
Cookies.set("user-uuid", res.uuid)
window.location = res.authorizeUrl
})
},
以上是在methods中,我们看到直接请求了PreLoginByGitee:
export function PreLoginByGitee() {
return request({
url: '/PreLoginByGitee',
headers: {
isToken: false
},
method: 'get',
})
}
对应的后端接口:
在ruoyi-admin下创建login包,专门存放第三方登录文件
@GetMapping("/PreLoginByGitee")
public AjaxResult PreLoginByGitee() {
AjaxResult ajax = AjaxResult.success();
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("1712ae8e8105c0005da36339ed72c1a6aae86322fc64d431dadcaa275a14be45")
.clientSecret("87fb8f83efc04fcd85696d5461c80ce6f98c94845539f9612024e81302111a05")
.redirectUri("http://localhost/callback")
.build());
String uuid = IdUtils.fastUUID();
String authorizeUrl = authRequest.authorize(uuid);
//存储
ajax.put("authorizeUrl", authorizeUrl);
ajax.put("uuid", uuid);
return ajax;
}
以上的代码是生成跳转路径。生成一个gitee的路径,在该页面gitee只要登录完成,gitee程序会自动跳转到我们之前设置好的回调地址。这里我们发现我们一直拿着一个uuid在传来传去,还传去了前端,它有什么鸟用呢?
authRequest.authorize(uuid)用到了uuid,并且后面要执行:
authRequest.login(AuthCallback.builder().state(uuid).code(code).build());
要保证俩uuid为同一个,所以uuid才传来传去。
关于本篇文章,文字有点难以描述,请大家看视频。
上面window.location = res.authorizeUrl
让我们进入了如下界面:
只要我们登陆好gitee,gitee会自动跳转到我们的回调地址。此时回调到了前端的下面的路由:
我们还提前准备好了组件:
<template>
<div v-loading="loading" style="height: 100%;width: 100%;">
正在加载中...
</div>
</template>
<script>
import Cookies from "js-cookie";
export default {
name: "loginByGitee",
data() {
return {
loading: true
}
},
mounted() {
this.loading = true;
console.log("uuid", Cookies.get("user-uuid"))
const formBody = {
uuid: Cookies.get("user-uuid"),
code: this.$route.query.code
}
this.$store.dispatch("LoginByGitee", formBody).then(() => {
this.$router.push({path: this.redirect || "/"}).catch(() => {
});
}).catch(() => {
this.loading = false;
});
}
}
</script>
<style scoped>
</style>
配置了组件,再配置路由
从上面可以看到,又带着了uuid,执行LoginByGitee方法,如下:
LoginByGitee({commit}, body) {
return new Promise((resolve, reject) => {
loginByGitee(body.code, body.uuid).then(res => {
setToken(res.token)
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
继续追代码:
export function loginByGitee(code, uuid) {
const data = {
code,
source: "Gitee",
uuid
}
return request({
url: '/loginByGitee',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
追到底了,调用的后端的:
@PostMapping("/loginByGitee")
public AjaxResult loginByGitee(@RequestBody LoginByOtherSourceBody loginByOtherSourceBody) {
AjaxResult ajax = AjaxResult.success();
String token = loginService
.loginByOtherSource(loginByOtherSourceBody.getCode(), loginByOtherSourceBody.getSource(), loginByOtherSourceBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}
补充LoginByOtherSourceBody实体类
package com.ruoyi.common.core.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginByOtherSourceBody {
private String code;
private String source;
private String uuid;
}
service层:
public String loginByOtherSource(String code, String source, String uuid) {
//先到数据库查询这个人曾经有没有登录过,没有就注册
// 创建授权request
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
.clientId("1712ae8e8105c0005da36339ed72c1a6aae86322fc64d431dadcaa275a14be45")
.clientSecret("87fb8f83efc04fcd85696d5461c80ce6f98c94845539f9612024e81302111a05")
.redirectUri("http://localhost/callback")
.build());
AuthResponse<AuthUser> login = authRequest.login(AuthCallback.builder().state(uuid).code(code).build());
System.out.println(login);
//先查询数据库有没有该用户
AuthUser authUser = login.getData();
SysUser sysUser = new SysUser();
sysUser.setUserName(authUser.getUsername());
sysUser.setSource(authUser.getSource());
List<SysUser> sysUsers = userService.selectUserListNoDataScope(sysUser);
if (sysUsers.size() > 1) {
throw new ServiceException("第三方登录异常,账号重叠");
} else if (sysUsers.size() == 0) {
//相当于注册
sysUser.setNickName(authUser.getNickname());
sysUser.setAvatar(authUser.getAvatar());
sysUser.setEmail(authUser.getEmail());
sysUser.setRemark(authUser.getRemark());
userService.registerUserAndGetUserId(sysUser);
AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER,
MessageUtils.message("user.register.success")));
} else {
sysUser = sysUsers.get(0);
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
//注册成功或者是已经存在的用户
LoginUser loginUser =
new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissionService.getMenuPermission(sysUser));
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
似乎如此就完事了。但实际上有很多细节我都没说。在视频里面说。
在SysUser实体类添加private String source;属性
1.在数据库中添加source字段
2.xml各个sql添加source
service添加方法
为了去掉权限的限制
重写插入方法,让他放回long类型
其实sql是一样的,只是为了返回的类型不同
细节1:前端白名单放行:
const whiteList = ['/login', "/callback", '/auth-redirect', '/bind', '/register']
细节2:字段source。如下代码可以获得登录source:
AuthUser authUser = login.getData();
source表示登录平台,如微信登录,支付宝登录,因为要确定用户的唯一性。username在不同的平台可能会重复,但是username+source就不会重复了。
细节3:新写了一个查询方法,如下:
userService.selectUserListNoDataScope(sysUser);
@Override
public List<SysUser> selectUserListNoDataScope(SysUser user) {
return userMapper.selectUserList(user);
}
新重写一个查询的原因是原来的的方法有数据权限:
细节4:后端安全配置放行了若干接口:
.antMatchers("/login", "/register", "/captchaImage", "/loginByGitee", "/PreLoginByGitee").anonymous()
细节5:当我们第一次登录成功,什么权限都没有,需要admin设置一下权限,或者在注册的时候给新用户一个“普通角色”。
细节6:新登录用户,头像根本显示不了,改成如下就能显示了【文件user.js】:
let avatar;
if (user.avatar == "" || user.avatar == null) {
avatar = require("@/assets/images/profile.jpg")
} else if (user.avatar.startsWith("http")) {
avatar = user.avatar
} else {
avatar = process.env.VUE_APP_BASE_API + user.avatar;
}
其他细节不再赘述。