GitLab授权流程
前期准备
- 需要服务端支持,Node.js
- 如果需要将GitLab的Token和其他服务关联,以达到一个Token即可以访问GitLab和其他服务,则需要后端支持
授权流程
-
用户点击授权
-
重定向到GitLab授权页面
//gitlab-xxx.com/oauth/authorize
query参数:
response_type: 授权类型,授权码为 'code'
redirect_uri: 回调URL
scope:授权的权限
client_id: 应用ID
输入账号密码,登录
PS: 首次登录该应用需要授权
-
GitLab重定向到回调URL
query参数中携带授权码
//192.168.1.10:3090/oauth/authorize?code=cdc03d4d23333cbcfcdb58c736762af854bc
-
根据授权码获取Token和用户信息
-
如果是私有化部署,需要将GitLab地址改为部署地址
授权步骤
此案例讨论Token关联的情况,需要后端支持
-
创建应用
获取应用ID(clientID)、应用密钥(clientSecret)
注意:
回调URL(Redirect URI)
- 不能使用localhost
- 不能直接写前端项目地址
- 需要关联Token的,请填写后端服务的接口地址,GitLab返回的用户信息&Token后端需要处理
-
安装相关依赖
npm install passport-gitlab2 express-session
-
在Node.js中添加依赖信息
iniconst passport = require('passport'); const session = require('express-session'); const GitLabStrategy = require('passport-gitlab2').Strategy;
-
启用会话的定义描述
php// session 配置,要在接口路由之前 SERVER.use(session({ secret: '自己项目的密钥', resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: false, // 开发环境设为 false maxAge: 1000 * 60 * 30 } }));
-
使用passport中间件
iniSERVER.use(passport.initialize()); SERVER.use(passport.session());
-
描述认证URL和回调URL的定义
typescript// 触发 GitLab OAuth 授权 ROUTER.get('/auth/gitlab', passport.authenticate('gitlab')); // GitLab OAuth 授权处理回调 ROUTER.get('/auth/gitlab/callback', (req: any, res: any) => { const QUERY: ITokenQuery = req.query; const SESSION = req.session; // 设置 token 信息 const TOKEN: IToken = { access_token: QUERY.access_token, created_at: QUERY.expires_in, refresh_token: QUERY.refresh_token, scope: GITLAB_AUTH.scope, token_type: QUERY.token_type }; // 在 session 中设置 token 信息 if (SESSION) { SESSION[IP_CONFIG.store.token] = TOKEN; } const GITLAB_API = new Gitlab({ host: GITLAB_AUTH.baseURL, oauthToken: QUERY.access_token?.toString() || '', }); // 获取用户信息 GITLAB_API.Users.showCurrentUser().then((user) => { if (user !== null && SESSION) { SESSION[IP_CONFIG.store.userInfo] = user; } // 授权成功重定向到加载页面,设置Token和用户信息,完成后跳转到指定系统页面 res.redirect('/ipmanage/blank/loading'); }).catch((error) => { // eslint-disable-next-line no-console console.error('error=========', error); res.redirect('/ipmanage/blank/loading'); }); });
-
定义了从GitLab返回的认证处理
less// Passport 策略配置 passport.use( new GitLabStrategy({ baseURL: GITLAB_AUTH.baseURL, authorizationURL: GITLAB_AUTH.authorizationURL, clientSecret: GITLAB_AUTH.clientSecret, clientID: GITLAB_AUTH.clientID, callbackURL: GITLAB_AUTH.callbackURL, scope: GITLAB_AUTH.scope, // 设置回调函数第一个参数为 request passReqToCallback: true }, // 回调函数,如果回调地址为Node接口,接口使用passport.authenticate('gitlab')才会调用次回调 (request: IncomingMessage, accessToken: string, refreshToken: string, profile: any, done: any) => { }) );
如果回调URL为后端接口地址,则不会触发此回调
-
定义获取Session中的Token和用户信息的接口
由于Node.js是服务端,无法操作SessionStorage和LocalStorage
所以需要前端调用Node服务端接口获取再保存
iniROUTER.get('/get-session', (req: any, res: any) => { const SESSION = req.session; if (SESSION) { const DATA: NodeSessionResult = { token: SESSION[IP_CONFIG.store.token], userInfo: SESSION[IP_CONFIG.store.userInfo] }; res.json(successHandle(req, DATA, 'Token和用户信息获取成功')); } else { res.status(500).json(errorHandle(req, -1, 'Token和用户信息获取失败')); } });
-
前端触发GitLab OAuth授权
login.component.html
xml<!-- 登录页面 --> <button nz-button class="login-form-button login-form-margin" [nzType]="'primary'" (click)="authLogin()">登录</button>
login.component.ts
iniauthLogin() { const URL = '/apis/auth/gitlab'; // 重定向到GitLab授权页面 window.location.href = URL; }
-
前端定义获取用户信息的接口
yunAdmin.service.ts
kotlin/** * @description 获取Session中的用户&登录信息 */ getNodeSessionInfo(): Observable<NodeSessionResult> { const URL = '/apis/get-session'; return this.http.get<NodeSessionResult>(URL) .pipe(tap(data => this._logger.log('获取Session信息', { params: {}, result: data }))); }
-
加载页面获取用户信息&Token
地址:/ipmanage/blank/loading 对应的页面
javascript/** * 获取登录数据 */ getLoginData() { // 获取session中的token和用户信息 this.yunAdminService.getNodeSessionInfo().subscribe((data: NodeSessionResult) => { // 设置用户信息&token信息 // ..... // 跳转至系统信息页面 this.router.navigate(['/default/system-info']); }, (error) => { this.page = 'error'; return throwError(error); }); }
-
涉及到的类型
css/** * gitlab token-query */ export interface ITokenQuery { access_token: string; expires_in: string; refresh_token: string; token_type: string; } /** * Node服务-Session信息 */ export interface NodeSessionResult { token: IToken; userInfo: IUserInfo; } /** * gitlab token */ export interface IToken { access_token: string; created_at: string; refresh_token: string; scope: string; token_type: string; }
使用Node处理GitLab返回的信息
-
修改
get('/auth/gitlab/callback')
方法less// GitLab OAuth 授权处理回调 ROUTER.get('/auth/gitlab/callback', // 使用passprot中间件处理 passport.authenticate('gitlab', { successRedirect: '/ipmanage/blank/loading', failureRedirect: '/ipmanage/blank/loading' }), (req: any, res: any) => { // 设置Token和用户信息,完成后跳转到指定系统页面 // ...Code // 授权成功重定向到前端页面 res.redirect('/ipmanage/dashboard'); });
-
修改Passport 策略配置的回调
less// Passport 策略配置 passport.use( new GitLabStrategy({ baseURL: GITLAB_AUTH.baseURL, authorizationURL: GITLAB_AUTH.authorizationURL, clientSecret: GITLAB_AUTH.clientSecret, clientID: GITLAB_AUTH.clientID, callbackURL: GITLAB_AUTH.callbackURL, scope: GITLAB_AUTH.scope, // 设置回调函数第一个参数为 request passReqToCallback: true }, // 回调函数,如果回调地址为Node接口,接口使用passport.authenticate('gitlab')才会调用次回调 (request: IncomingMessage, accessToken: string, refreshToken: string, profile: any, done: any) => { // 处理用户信息 if (profile) { return done(null, { id: profile.id }); } return done(null, false); }) );
可能遇到的问题
-
直接使用GET方式调用GitLab OAuth 授权接口,出现跨域
尽管授权接口使用了代理,但在GitLab授权重定向时,域名和协议都发生了变化
解决方案:
使用
window.location.href
ini// 重定向到GitLab授权页面 window.location.href = URL;
-
Node.js使用session会话报错: session is undefined
解决方案:session 配置,要在接口路由之前
php// session 配置,要在接口路由之前 SERVER.use(session({ ...options })); // Example Express Rest API endpoints SERVER.use('/api', ROUTER);
-
Passport 策略配置回调方法:done is not a function
参数 passReqToCallback
如果设置为true, 回调方法第一个参数为request
typescriptnew GitLabStrategy({ ...options, passReqToCallback: true }, (request: IncomingMessage, accessToken: string, refreshToken: string, profile: any, done: any) => { // 处理用户信息 if (profile) { return done(null, { id: profile.id }); } return done(null, false); })
如果不设置,或设置为false
需要去除第一个request参数
typescriptnew GitLabStrategy({ ...options }, (accessToken: string, refreshToken: string, profile: any, done: any) => { // 处理用户信息 if (profile) { return done(null, { id: profile.id }); } return done(null, false); })