1. 什么是单点登录系统?
单点登录(Single Sign-On,简称SSO)是指用户只需登录一次,就可以访问多个应用系统。在传统的非SSO系统中,用户需要为每个应用系统分别进行登录,这样不仅繁琐,而且容易忘记用户名和密码。而SSO系统则可以避免这些问题,提高用户的使用体验。
在SSO系统中,用户只需要进行一次身份验证,然后系统就会为其颁发一个令牌(Token)。用户访问其他应用系统时,只需要携带该令牌即可,而无需再次输入用户名和密码。
SSO系统的主要优点包括:
提高用户体验:用户只需登录一次,就可以访问多个应用系统,避免了频繁的登录和输入用户名密码的麻烦。
提高安全性:SSO系统可以通过集中管理用户的身份验证和授权,从而提高系统的安全性。
减少系统开发成本:SSO系统可以提供通用的身份验证和授权服务,各个应用系统只需要集成SSO系统即可,从而减少了系统开发的成本。
2. 如何使用Node.js实现SSO系统?
下面我们将介绍如何使用Node.js实现一个简单的SSO系统。在本文中,我们将使用以下技术栈:
Express.js:Node.js的Web框架,用于构建Web应用程序
Passport.js:一个Node.js的认证框架,用于管理用户的身份验证和授权
JWT(JSON Web Token):一种轻量级的授权和身份验证方案,用于颁发令牌
2.1 创建Express.js应用程序
首先,我们需要创建一个新的Node.js应用程序。打开终端,并执行以下命令:
$ mkdir sso-demo
$ cd sso-demo
$ npm init -y
然后,我们可以使用以下命令安装Express.js和Passport.js:
$ npm install express passport passport-local jwt-simple
其中,passport-local用于本地身份验证,jwt-simple用于颁发和解析JWT令牌。
2.2 创建Passport.js策略
为了使用Passport.js进行身份验证,我们需要创建一个名为passport.js的新文件,并配置Passport.js策略。在该文件中,我们将使用本地策略进行用户身份验证。打开passport.js文件,并添加以下内容:
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy({ usernameField: 'email' },
(email, password, done) => {
// TODO:添加身份验证逻辑
}
));
在上面的代码中,我们使用LocalStrategy初始化Passport.js策略,并将email作为用户名字段。然后,我们可以在回调函数中添加自定义的身份验证逻辑。
2.3 创建用户模型
接下来,我们需要创建一个用户模型。打开一个名为user.js的新文件,并添加以下内容:
const users = [
{ id: '1', email: 'test1@example.com', password: 'password1' },
{ id: '2', email: 'test2@example.com', password: 'password2' },
{ id: '3', email: 'test3@example.com', password: 'password3' },
];
exports.findByEmail = async function(email) {
return users.find(user => user.email === email);
};
exports.findById = async function(id) {
return users.find(user => user.id === id);
};
在这个文件中,我们定义了三个测试用户,并编写了两个查询函数,用于查找用户模型。
2.4 创建JWT令牌
现在我们可以编写一个名为jwt.js的新文件,用于颁发JWT令牌。打开该文件,并添加以下内容:
const jwt = require('jwt-simple');
const moment = require('moment');
const secret = 'sso-demo-secret'; // JWT令牌的密钥
exports.createToken = function(user) {
const payload = {
sub: user.id,
iat: moment().unix(),
exp: moment().add(10, 'minutes').unix(),
};
return jwt.encode(payload, secret);
};
在上面的代码中,我们使用jwt-simple和moment库创建JWT令牌。首先,我们定义了令牌的秘密密钥。然后,我们定义了一个createToken函数,该函数接受一个用户对象作为参数,并返回一个JWT令牌。
在createToken函数中,我们定义了令牌的负载(Payload)。其中,sub字段表示用户ID,iat字段表示JWT令牌的发放时间,exp字段表示JWT令牌的过期时间。然后,我们使用jwt-simple库将负载编码为JWT令牌,并返回该令牌。
2.5 编写登录路由
现在我们可以在Express.js应用程序中添加一个登录路由,用于实现用户身份验证。打开主应用程序文件(app.js或index.js),并添加以下内容:
const passport = require('passport');
const { Strategy } = require('passport-local');
const bodyParser = require('body-parser');
const user = require('./user');
const jwt = require('./jwt');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());
passport.use(new Strategy({
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
const user = await user.findByEmail(email);
if (!user) return done(null, false);
if (password !== user.password) return done(null, false);
return done(null, user);
} catch (err) {
return done(err);
}
}
));
app.post('/login',
passport.authenticate('local', { session: false }),
(req, res) => {
const token = jwt.createToken(req.user);
res.json({ token });
});
在上面的代码中,我们首先使用body-parser库解析请求的JSON主体。然后,我们初始化了Passport.js并配置了本地策略。在本地策略的回调函数中,我们使用findByEmail函数查找用户,然后比较用户的密码。如果身份验证通过,我们将已验证的用户对象放在请求对象(req.user)中,并调用next函数,以便将请求转发给路由处理程序。
最后,我们编写了一个名为'/login'的路由,用于处理用户登录请求。在该路由中,我们使用Passport.js的authenticate函数和本地策略进行身份验证,并使用createToken函数创建JWT令牌。如果身份验证通过,我们将JWT令牌包含在响应中。
2.6 编写保护路由
现在我们已经成功地创建了一个登录路由和JWT令牌。接下来,我们可以编写一个受保护的路由,该路由只有已通过身份验证的用户才能访问。打开主应用程序文件,并添加以下内容:
const jwt = require('jwt-simple');
const app = express();
function ensureAuthenticated(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).send({ message: '请提供JWT令牌' });
}
const token = req.headers.authorization.split(' ')[1];
try {
const decoded = jwt.decode(token, 'sso-demo-secret');
if (decoded.exp <= Date.now()) {
return res.status(401).send({ message: 'JWT令牌已过期' });
}
req.user = decoded.sub;
next();
} catch (err) {
return res.status(401).send({ message: 'JWT令牌无效' });
}
}
app.get('/protected', ensureAuthenticated, (req, res) => {
res.send({ message: `Hello, user ID ${req.user}!` });
});
在上面的代码中,我们首先定义了一个名为ensureAuthenticated的中间件函数,该函数用于检查JWT令牌是否有效。如果JWT令牌无效,则返回401(未经授权)错误。如果JWT令牌有效,则将用户ID添加到请求对象中,并调用next函数,以便将请求转发给下一个路由处理程序。
然后,我们编写了一个名为'/protected'的路由,该路由使用ensureAuthenticated中间件进行保护,并返回包含当前用户ID的响应。
2.7 测试API端点
现在我们已经完成了SSO系统的实现。为了测试API端点,我们可以使用Postman或其他HTTP客户端向'/login'路由发送POST请求,并包含JSON主体(例如:{ "email": "test1@example.com", "password": "password1" })。如果身份验证通过,服务器应该返回一个包含JWT令牌的响应。
我们可以使用JWT令牌向'/protected'路由发送GET请求,以查看当前用户ID。
3. 总结
在本文中,我们介绍了什么是单点登录系统,以及如何使用Node.js和Passport.js实现SSO系统。我们使用Express.js构建了Web应用程序,并使用Passport.js进行身份验证,JWT令牌作为访问令牌。最后,我们编写了一个保护路由,该路由只有经过身份验证的用户才能访问。
SSO系统具有许多优点,例如提高用户体验,提高安全性以及减少系统开发成本。使用Node.js和相关工具可以轻松实现SSO系统,并提高应用程序的安全性和可维护性。