SSO
认证中心
认证中心,服务搭建 passport/app.js
const http = require('http');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const login = require('./routes/login');
const checkToken = require('./routes/check-token');
const app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use('/login', login);
app.use('/check_token', checkToken);
let port = process.env.PORT || 8080;
app.set('port', port);
let server = http.createServer(app);
server.listen(port, function () {
console.log(`Server passport listening on port: ${port}`);
});
认证中心,登录页面 passport/views/login.ejs
<!DOCTYPE html>
<html>
<head>
<title>统一登录passport</title>
</head>
<body>
<h1>统一登录passport</h1>
<form method="post">
<div>用户名:<input type="text" name="name"/></div>
<div>密码:<input type="text" name="password"/></div>
<div><input type="submit" value="登录"/></div>
</form>
</body>
</html>
认证中心,登录路由及逻辑 passport/routes/login.js
'use strict';
const express = require('express');
const service = require('../service');
const router = express.Router();
router.get('/', function (req, res, next) {
let cookies = req.cookies;
let token = cookies.token;
/*
* 如果 token 存在,说明登陆过,检查 token 是否合法。合法则重定向到原页面,并将 token 作为参数传递。
* 原页面对应的系统在收到带有 token 的请求后,应该向 passport 发起请求检查 token 的合法性。
*
* 如果 cookie 中 token 不存在或者不合法,则返回登录页面。这里登录页面由 passport 提供,也可以重定向到原系统的登录页面。
*/
if (token && service.isTokenValid(token)) {
let redirectUrl = req.query.redirectUrl;
if (redirectUrl) {
res.redirect(`http://${redirectUrl}?token=${token}`);
} else {
// TODO 如果不含有重定向页面,可以返回系统首页。这里直接返回一个登录成功的信息。
res.send('<h1>登录成功!</h1>');
}
} else {
res.render('login');
}
});
router.post('/', function (req, res, next) {
let body = req.body;
let name = body.name;
let password = body.password;
// FIXME 密码验证
if (name === 'test' && password === '123456') {
// TODO token 应该按照一定的规则生成,并持久化。
let token = 'passport';
res.cookie('token', token, {
maxAge: 1000 * 60 * 60 * 24 * 30,
httpOnly: true
});
if (req.query.redirectUrl) {
res.redirect('http://' + req.query.redirectUrl + '?token=' + token);
} else {
res.send('<h1>登录成功!</h1>');
}
} else {
res.send({
error: 1,
msg: '用户名或密码错误'
});
}
});
module.exports = router;
认证中心,Token检查路由 passport/routes/check-token.js
'use strict';
const express = require('express');
const service = require('../service');
const router = express.Router();
router.get('/', function (req, res, next) {
var token = req.query.token;
var result = {
error: 1, //登录失败
};
if (service.isTokenValid(token)) {
result.error = 0;
result.userId = 'test';
}
res.json(result);
});
module.exports = router;
Token检查逻辑 passport/service/index.js
'use strict';
/**
*
* @param {String} token
* @return {Boolean}
*/
function isTokenValid(token) {
// TODO 从存储系统中查找相应 token 信息,判断 token 的合法性
if (token && token === 'passport') {
return true;
}
return false;
}
module.exports = {
isTokenValid
};
测试系统
测试系统,服务搭建 system/app.js
'use strict';
const express = require('express');
const path = require('path');
const session = require('express-session');
const http = require('http');
const index = require('./routes/index');
const app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
/*
* 这里设置 cookie 中 sessionID 的过期时间为默认,即浏览器关闭后失效,并且 session 直接保存在内存中。
*
* 通过 cookie.maxAge 设置 cookie 中 sessionID 的过期时间,可以使 sessionID 的保存时间更久,并且 session 持久化。
*/
app.use(session({
secret: 'passport',
resave: false,
saveUninitialized: false,
}));
app.use('/', index);
let port = process.env.PORT || 8081;
app.set('port', port);
let server = http.createServer(app);
server.listen(port, function () {
console.log(`Server ${process.env.SERVER_NAME} listening on port: ${port}`);
});
测试系统,业务页面 system/views/index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= system %> 系统-www.<%= system %>.com</title>
</head>
<body>
<h1>你好,<%= user %>!欢迎访问 <%= system %> 系统。</h1>
</body>
</html>
测试系统,业务路由 system/routes/index.js
"use strict";
const express = require('express');
const router = express.Router();
const request = require('request');
router.get('/', function (req, res, next) {
let system = process.env.SERVER_NAME;
let user = req.session.user;
if (user) {
// 如果 session 中有用户信息,则说明已经登录过,直接返回请求的资源。
res.render('index', {
user: user,
system: system
});
} else {
/*
* 如果 session 中没有用户信息,则需要去 passport 系统进行身份认证。这里区分两种情况:
*
* 1. 如果 url 中带有 token 信息,则去 passport 中认证 token 的有效性,如果有效则说明登录成功,建立 session 开始通话。
* 2. 如果 url 中没有 token 信息,则取 passport 进行登录。如果登录成功,passport 会将浏览器重定向到此系统并在 url 上附带 token 信息。进行步骤 1。
*
* 因为 token 很容易伪造,所以需要去检验 token 的真伪,否则任何一个带有 token 的请求岂不是都可以通过认证。
*/
let token = req.query.token;
if (!token) {
res.redirect(`http://localhost:8080/login?redirectUrl=${req.headers.host + req.originalUrl}`);
} else {
request(
`http://localhost:8080/check_token?token=${token}&t=${new Date().getTime()}`,
function (error, response, data) {
if (!error && response.statusCode === 200) {
data = JSON.parse(data);
if (data.error === 0) {
// TODO 这里的 userId 信息应该是经过加密的,加密算法要么内嵌,要么从 passport 获取。这里为了操作简单,直接使用明文。
let userId = data.userId;
if (!userId) {
res.redirect(`http://localhost:8080/login?redirectUrl=${req.headers.host + req.originalUrl}`);
return;
}
/*
* TODO
* 获取 userId 后,可以操作数据库获取用户的详细信息,用户名、权限等;这里也可以由 passport 直接返回 user 信息,主要看用户信息
* 的数据库如何部署。
* 为了方便,直接操作 userId,省略用户数据库操作。
*/
req.session.user = userId;
res.render('index', {
user: userId,
system: system
});
} else {
// token 验证失败,重新去 passport 登录。
res.redirect(`http://localhost:8080/login?redirectUrl=${req.headers.host + req.originalUrl}`);
}
} else {
res.redirect(`http://localhost:8080/login?redirectUrl=${req.headers.host + req.originalUrl}`);
}
});
}
}
});
module.exports = router;
启动多个测试服务
PORT=8081 SERVER_NAME=a node app.js
PORT=8082 SERVER_NAME=b node app.js