用户登录【详解】(含码云 gitee 的实现范例)"/>
OAuth2 完成用户登录【详解】(含码云 gitee 的实现范例)
什么是 OAuth2 登录?
OAuth2 是当下广泛流行的第三方应用授权登录方案,如微信扫码登录。
OAuth2 的工作原理
OAuth2 实战范例
前端第三方登录图标
以码云 gitee 为例,在登录页面添加登录图标(vue+element)
核心代码
<!-- gitee登录 --><el-avatarclass="bnt"size="medium":src="giteeUrl"@click.native="loginByGitee"></el-avatar>
import giteeLogo from "@/assets/images/giteeLogo.svg";
data() 中
giteeUrl: giteeLogo,
loginByGitee() {// 后端服务器跳转到gitee登录页的接口window.open("http://127.0.0.1:7002/api/users/passport/gotoGitee");},
后端跳转到第三方应用
以 egg.js 搭建的后端服务器为例
app\router.ts
// 跳转到 gitee 官网进行登录认证router.get('/api/users/passport/gotoGitee', controller.user.gotoGitee);
app\controller\user.ts
async gotoGitee() {const { app, ctx } = this;const { cid, redirectURL } = app.config.giteeOauthConfig;ctx.redirect(`=${cid}&redirect_uri=${redirectURL}&response_type=code`);}
config\config.default.ts
// gitee 的认证配置const giteeOauthConfig = {cid: process.env.GITEE_CID,secret: process.env.GITEE_SECRET,redirectURL: 'http://localhost:7002/api/users/passport/gitee/callback',authURL: '=authorization_code',giteeUserAPI: '',};
.env
GITEE_CID = "e7440791b41de36978e65b258228e26b5d746**********"
GITEE_SECRET = "c1744f6a7224182b3c39df2b76f2608e750a46c4f907b3569*****"
GITEE_CID 和 GITEE_SECRET 来自码云自动生成的密钥,创建方法如下:
- 登录码云官网,进入设置页
/
- 打开第三方应用
- 创建应用
- 录入相关信息
自定义需使用 OAuth2 登录的应用名称
上传需使用 OAuth2 登录的应用的logo,填写应用的域名(暂时没购买也没关系),填写应用回调地址,通常对应后端接口。
5. 点击创建应用按钮
默认登录后可以访问获取用户信息的接口,若需访问其他接口,可根据需要打钩
6. 完成创建后,便会得到 GITEE_CID 和 GITEE_SECRET
后端响应第三方应用授权后的回调生成 token
app\router.ts
// 通过gitee登录的回调router.get('/api/users/passport/gitee/callback',controller.user.oauthByGitee);
app\controller\user.ts
async oauthByGitee() {const { ctx } = this;const { code } = ctx.request.query;try {// 生成 tokenconst token = await ctx.service.user.loginByGitee(code);// 渲染授权成功页面(向前端传递token)await ctx.render('success.nj', { token });} catch (e) {return ctx.helper.error({ ctx, errorType: 'giteeOauthError' });}}
app\service\user.ts
async loginByGitee(code: string) {const { ctx, app } = this;// 获取 access_tokenconst accessToken = await this.getAccessToken(code);// 获取用户的信息const user = await this.getGiteeUserData(accessToken);// 检查用户是否已注册const { id, name, avatar_url, email } = user;// id 默认为数字类型,此处转换为字符串类型const stringId = id.toString();// 为避免不同平台id相同,此处在id前加上平台名称,如Gitee + id,Github + id// 假如已经存在,直接返回tokenconst existUser = await this.findByAccount(`Gitee${stringId}`);if (existUser) {const token = app.jwt.sign({ account: existUser.account, _id: existUser._id },app.config.jwt.secret,{ expiresIn: app.config.jwtExpires });return token;}// 假如不存在,新建用户后返回 tokenconst userCreatedData: Partial<UserProps> = {oauthID: stringId,provider: 'gitee',account: `Gitee${stringId}`,picture: avatar_url,nickName: name,email,type: 'oauth',};const newUser = await ctx.model.User.create(userCreatedData);const token = app.jwt.sign({ account: newUser.account, _id: newUser._id },app.config.jwt.secret,{ expiresIn: app.config.jwtExpires });return token;}
获取 access_token
async getAccessToken(code: string) {const { ctx, app } = this;const { cid, secret, redirectURL, authURL } = app.config.giteeOauthConfig;const { data } = await ctx.curl(authURL, {method: 'POST',contentType: 'json',dataType: 'json',data: {code,client_id: cid,redirect_uri: redirectURL,client_secret: secret,},});app.logger.info(data);return data.access_token;}
获取用户信息
async getGiteeUserData(access_token: string) {const { ctx, app } = this;const { giteeUserAPI } = app.config.giteeOauthConfig;const { data } = await ctx.curl<GiteeUserResp>(`${giteeUserAPI}?access_token=${access_token}`,{dataType: 'json',});return data;}
后端渲染页面向前端传递 token
app\view\success.nj
<!doctype html>
<html class="no-js" lang=""><head><meta charset="utf-8"><title>授权成功</title><meta name="description" content=""><meta name="viewport" content="width=device-width, initial-scale=1">
</head><body><h1>授权成功</h1>
</body>
<script>window.onload = function() {setTimeout(() => {const message = {type: 'oauth-token',token: '{{token}}'}window.opener.postMessage(message, 'http://localhost:8080')window.close()}, 1000)}
</script>
</html>
此处使用 window.opener.postMessage 实现了跨域传参,其详细用法可参考
/
前端监听后端消息获取到 token
获取到 token 后存入sessionStorage,并获取用户信息
mounted() {window.addEventListener("message", (res) => {const { type, token } = res.data;sessionStorage.setItem("token", token);if (type === "oauth-token") {this.$http.get("/api/api/users/getUserInfo").then((res) => {this.userInfo = res;sessionStorage.setItem("userName", res.data.data.nickName);this.$router.push("/index");});}});},
针对需 token 的接口,统一添加 token
src\axios.js
// 添加请求拦截器
axios.interceptors.request.use(function (config) {// 无需带token的请求路径,正则校验(/public 和 /login 开头的api 无需token )let publicPath = [/^\/public/, /^\/login/];// 是否是公开接口(公开接口无需token)let isPublic = false;// 判断当前api是否是公开接口publicPath.map((path) => {isPublic = isPublic || path.test(config.url);});// 从sessionStorage中获取tokenconst token = sessionStorage.getItem("token");if (!isPublic && token) {// 若当前api不是公开接口,并且token存在,则向headers中添加tokenconfig.headers.Authorization = "Bearer " + token;}
axios 更详细的使用,详见
前端首页获取用户信息更新登录状态
mounted() {this.userName = sessionStorage.getItem("userName");},
<div class="loginBox" v-if="!userName"><el-button type="text" class="btn" @click="gotoLogin">登录</el-button><el-divider direction="vertical"></el-divider><el-button type="text" class="btn" @click="gotoRegister">注册</el-button></div><div class="helloBox" v-else><span>欢迎你,{{ userName }} !</span><el-button @click="logout" type="text" class="btn">退出</el-button></div></div>
更多推荐
OAuth2 完成用户登录【详解】(含码云 gitee 的实现范例)
发布评论