Express 实现 简易 SSO

简介: Express 实现 简易 SSO

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
相关文章
|
3月前
|
JavaScript 前端开发 Linux
【Azure 应用服务】NodeJS Express + MSAL 实现API应用Token认证(AAD OAuth2 idToken)的认证实验 -- passport.authenticate()
【Azure 应用服务】NodeJS Express + MSAL 实现API应用Token认证(AAD OAuth2 idToken)的认证实验 -- passport.authenticate()
|
11月前
【Express】 —利用 Express 托管静态文件
【Express】 —利用 Express 托管静态文件
|
11月前
【Express】—Express路由请求
【Express】—Express路由请求
|
JSON JavaScript 前端开发
使用express搭建后端服务
使用express搭建后端服务
|
JSON 算法 中间件
在Express中使用JWT的操作与报错问题
在Express中使用JWT的操作与报错问题
349 0
|
中间件
Express中间件(上)
Express中间件(上)
125 2
Express中间件(上)
如何在Yii2.0项目中安装Jasny SSO?
如何在Yii2.0项目中安装Jasny SSO?
119 0
|
开发框架 PHP
Yii2.0是否支持集成Jasny SSO?
Yii2.0是否支持集成Jasny SSO?
132 0
Jasny SSO与Yii2.0的版本兼容性如何?
Jasny SSO与Yii2.0的版本兼容性如何?
|
JavaScript 中间件
Express自定义中间件
Express自定义中间件
149 2