├── .gitignore ├── README.md ├── client.js ├── controller.js ├── index.js ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | 12 | # idea ignore 13 | .idea/ 14 | *.ipr 15 | *.iml 16 | *.iws 17 | 18 | # temp ignore 19 | *.log 20 | *.cache 21 | *.diff 22 | *.patch 23 | *.tmp 24 | 25 | # system ignore 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # package ignore (optional) 30 | # *.jar 31 | # *.war 32 | # *.zip 33 | # *.tar 34 | # *.tar.gz 35 | 36 | node_modules/ 37 | runtime/ 38 | /prd/ 39 | /dev/ 40 | .tags 41 | .tags1 42 | tsconfig.json 43 | client/plugin-module.js 44 | .vscode 45 | /iconfont 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yapi-plugin-oauth2 2 | 3 | 第三方插件,基于Oauth2协议登录,在生成的配置文件中,添加如下配置即可: 4 | 5 | ``` 6 | "plugins": [ 7 | { 8 | "name": "qsso", 9 | "options": { 10 | "type": "oauth2", 11 | "hostscheme": "http", 12 | "hostname" : "your.oauth2server", 13 | "loginPath": "/api/v4/user", 14 | "authPath" : "/oauth/authorize", 15 | "tokenPath" : "/oauth/token", 16 | "redirectUri" : "http://your.yapiserver/api/plugin/oauth2/callback", 17 | "appId" : "xxxxxxxxxxxxxxxxxxxxxxxxxxx", 18 | "appSecret" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 19 | "emailKey" : "emailkey", 20 | "userKey" : "usernamekey", 21 | "emailPostfix" : "@yapi.com" 22 | } 23 | } 24 | ] 25 | ``` 26 | 使用注意: 27 | 28 | - Oauth2服务器用户信息需要提供: `email`和`username`两个字段,如果字段名不一致,可以通过`emailKey`和`userKey`设置,如果没有电子邮箱字段,可以使用用户名字段+`emailPostfix`属性设置默认电子邮箱地址(电子邮箱是Yapi用户唯一标志),如果有`emailKey`默认使用`emailKey`获取邮箱信息 29 | 30 | 这里面的配置项含义如下: 31 | 32 | - `hostscheme` oauth2服务器的访问协议 33 | - `hostname` oauth2服务器的访问地址 34 | - `loginPath` 获取用户信息路径 35 | - `authPath` 授权路径 36 | - `tokenPath` 获取access_token路径 37 | - `redirectUri` 重定向路径 38 | - `emailKey` 用户信息电子邮件字段key 39 | - `userKey` 用户信息用户名字段key 40 | - `emailPostfix` 邮箱后缀 41 | 42 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | module.exports = function (options) { 4 | const handleLogin = () => { 5 | const { hostscheme, hostname, redirectUri, authPath, appId } = options; 6 | let ret = encodeURIComponent(redirectUri); 7 | let redirectURL = hostscheme + "://" + hostname + authPath + '?client_id=' 8 | + appId + '&response_type=code&state=test1234&redirect_uri=' + ret; 9 | location.href = redirectURL; 10 | } 11 | 12 | const QssoComponent = () => ( 13 | 14 | ) 15 | 16 | this.bindHook('third_login', QssoComponent); 17 | }; 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /controller.js: -------------------------------------------------------------------------------- 1 | const baseController = require('controllers/base.js'); 2 | const yapi = require('yapi.js'); 3 | const http = require('http'); 4 | 5 | class oauth2Controller extends baseController { 6 | constructor(ctx) { 7 | super(ctx); 8 | } 9 | 10 | /** 11 | * oauth2回调 12 | * @param {*} ctx 13 | */ 14 | async oauth2Callback(ctx) { 15 | try { 16 | // 获取code和state 17 | let oauthcode = ctx.request.query.code; 18 | if (!oauthcode) { 19 | return (ctx.body = yapi.commons.resReturn(null, 400, 'code不能为空')); 20 | } 21 | let oauthstate = ctx.request.query.state; 22 | if (!oauthstate) { 23 | return (ctx.body = yapi.commons.resReturn(null, 400, 'state不能为空')); 24 | } 25 | let ops = yapi.WEBCONFIG.plugins[0].options; 26 | // 通过code获取token 27 | let tokenpath = ops.tokenPath + '?client_id=' + ops.appId + '&client_secret=' 28 | + ops.appSecret + '&code=' + oauthcode + "&grant_type=authorization_code&redirect_uri=" + encodeURIComponent(ops.redirectUri); 29 | let tokenResult = await this.requestInfo(ops, tokenpath, 'POST').then(function(res) { 30 | let jsonRes = JSON.parse(res); 31 | ctx.redirect('/api/user/login_by_token?token=' + jsonRes.access_token); 32 | }).catch(function(rej) { 33 | return { 34 | status_code: rej.statuscode, 35 | message: rej.statusMessage 36 | }; 37 | }); 38 | return ctx.body = yapi.commons.resReturn(tokenResult, 401, "授权失败"); 39 | } catch (err) { 40 | ctx.body = yapi.commons.resReturn(null, 400, err.message); 41 | } 42 | } 43 | 44 | /** 45 | * 请求封装 46 | * @param {*} host 47 | * @param {*} port 48 | * @param {*} path 49 | */ 50 | requestInfo(ops, path, method) { 51 | return new Promise((resolve, reject) => { 52 | let req = ''; 53 | let http_client = http.request( 54 | { 55 | host: ops.hostname, 56 | method: method, 57 | path: path 58 | }, 59 | function(res) { 60 | res.on('error', function(err) { 61 | reject(err); 62 | }); 63 | res.setEncoding('utf8'); 64 | if (res.statusCode != 200) { 65 | reject({statuscode: res.statusCode, statusMessage: res.statusMessage}); 66 | } else { 67 | res.on('data', function(chunk) { 68 | req += chunk; 69 | }); 70 | res.on('end', function() { 71 | resolve(req); 72 | }); 73 | } 74 | } 75 | ); 76 | http_client.on('error', (e) => { 77 | reject({message: 'request error'}); 78 | }); 79 | http_client.end(); 80 | }); 81 | } 82 | } 83 | 84 | module.exports = oauth2Controller; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | "yapi-plugin-qsso@1.1.0", 5 | "/Users/user/Documents/wenxing/projects/yapi/vendors" 6 | ] 7 | ], 8 | "_from": "yapi-plugin-qsso@1.1.0", 9 | "_id": "yapi-plugin-qsso@1.1.0", 10 | "_inBundle": false, 11 | "_integrity": "sha1-oqHquVQFcHxrZ26waX6WQvht0Sg=", 12 | "_location": "/yapi-plugin-qsso", 13 | "_phantomChildren": {}, 14 | "_requested": { 15 | "type": "version", 16 | "registry": true, 17 | "raw": "yapi-plugin-qsso@1.1.0", 18 | "name": "yapi-plugin-qsso", 19 | "escapedName": "yapi-plugin-qsso", 20 | "rawSpec": "1.1.0", 21 | "saveSpec": null, 22 | "fetchSpec": "1.1.0" 23 | }, 24 | "_requiredBy": [ 25 | "/" 26 | ], 27 | "_resolved": "http://registry.npm.taobao.org/yapi-plugin-qsso/download/yapi-plugin-qsso-1.1.0.tgz", 28 | "_spec": "1.1.0", 29 | "_where": "/Users/user/Documents/wenxing/projects/yapi/vendors", 30 | "author": "", 31 | "bugs": { 32 | "url": "https://github.com/YMFE/yapi-plugin-qsso/issues" 33 | }, 34 | "description": "第三方插件,在生成的配置文件中,添加如下配置即可:", 35 | "homepage": "https://github.com/YMFE/yapi-plugin-qsso#readme", 36 | "license": "ISC", 37 | "main": "index.js", 38 | "name": "yapi-plugin-qsso", 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/YMFE/yapi-plugin-qsso.git" 42 | }, 43 | "scripts": { 44 | "start": "node server.js", 45 | "test": "echo \"Error: no test specified\" && exit 1" 46 | }, 47 | "version": "1.1.0" 48 | } 49 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const controller = require('./controller'); 3 | 4 | module.exports = function (options) { 5 | const {emailPostfix, emailKey, userKey, hostscheme, hostname, loginPath} = options; 6 | 7 | this.bindHook('third_login', (ctx) => { 8 | let token = ctx.request.body.token || ctx.request.query.token; 9 | return new Promise((resolve, reject) => { 10 | request(hostscheme + "://" + hostname + loginPath + "?access_token=" + token, function (error, response, body) { 11 | if (!error && response.statusCode == 200) { 12 | let result = JSON.parse(body); 13 | if (result) { 14 | let ret = { 15 | email: (emailKey != undefined && emailKey.length > 0) ? result[emailKey] : (result[userKey] + emailPostfix), 16 | username: result[userKey] 17 | }; 18 | resolve(ret); 19 | } else { 20 | reject(result); 21 | } 22 | } 23 | reject(error); 24 | }); 25 | }); 26 | }); 27 | 28 | this.bindHook('add_router', function(addRouter) { 29 | addRouter({ 30 | controller: controller, 31 | method: 'get', 32 | path: 'oauth2/callback', 33 | action: 'oauth2Callback' 34 | }); 35 | }); 36 | } --------------------------------------------------------------------------------