什么是单点登录

单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

单点登录流程

Android 单点登录 手机app单点登录_javascript


1、分别有单点系统app1,app2,单点认证服务器sso

2、用户访问app1,发现未登录,重定向到sso服务器并将自己的请求地址作为参数

3、sso引导用户登录,登录成功将登录凭证设置到cookie中,建立全局会话,并把token携带重定向到app1。

4、app1拿到token后请求sso校验token是否有效,有效则建立局部会话,至此app1登录成功.

5、用户访问app2系统,发现未登录于是重定向到sso认证中心,并将app2的地址作为请求参数

6、app2重定向到sso时因为sso已登录成功,所以有sso服务上的cookie,sso校验cookie并携带token重定向到app2

7、app2拿到token请求sso校验,生效后则建立app2的局部会话,至此app2登录成功。

nodejs实现

  1. sso代码
const Koa = require('koa');
const app = new Koa();
const path = require('path')
const static = require('koa-static'); 
const views = require('koa-views');
const json = require('koa-json')

const koaBody = require('koa-body');
const logger = require('koa-logger')
const session = require('koa-session')

const index = require('./src/router/router')

const CONFIG = {
    key: 'koa.sess',
    maxAge: 86400000,
    autoCommit: true,
    overwrite: true,
    httpOnly: false,
    signed: true,
    rolling: false,
    renew: false,
    secure: false,
    sameSite: null,
  };
  app.use(views(path.join(__dirname,'./static')),{
    extension:'html'
  })
//   app.use(static(path.join(__dirname+'/static'),  // 开放的文件夹 __dirname为当前运行文件的目录  目前看来 只能开放文件夹 无法开放单独一个文件
// 	{
//   		index:true,       // 默认为true  访问的文件为index.html  可以修改为别的文件名或者false
//   		hidden:false,      // 是否同意传输隐藏文件
//   		defer:true,		   // 如果为true,则在返回next()之后进行服务,从而允许后续中间件先进行响应
// 	}
// ))
  app.keys = ['some secret hurr'];
app.use(session(CONFIG, app));


// middlewares
app.use(koaBody({
    multipart: true,    //解析多个文件
    formidable: {
        maxFileSize: 100 * 1024 * 1024,    // 设置上传文件大小最大限制,默认2M
        //uploadDir: 可以填写一个路径,不填写默认为 os.tmpDir()
    }
}))



  app.use(json())
app.use(index.routes(),index.allowedMethods())
// app.use()
app.use(logger())

// x-response-time

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});


// logger

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});


// response
// error-handling
app.on('error', (err, ctx) => {
    console.error('server error', err, ctx)
  });
  // app.use(ctx => {
  //   // ignore favicon
  //   if (ctx.path === '/favicon.ico') return;
  
  //   let n = ctx.session.views || 0;
  //   ctx.session.views = ++n;
  //   ctx.body = n + ' views';
  // });
app.listen(8383);

router代码

const Router  = require('koa-router')
const router = new Router();

const {
    v4: uuid
  } = require('uuid');
router.get('/login',async (ctx, next) => {
  console.log(ctx.cookies)
  const token = ctx.cookies.get('token')
  if(token){
    ctx.redirect(`${ctx.query.redirectUrl}?token=${token}`)
    return
  }
    await ctx.render('index.html')
  })
router.post('/login', async (ctx, next) => {
    console.log(ctx.querystring)
    let {
      username,
      password,
      origin
    } = ctx.request.body
  
    let userInfo = {
      username: 'admin',
      password: '123456'
    }
  
    if (username === userInfo.username && password === userInfo.password) {
      let tk = uuid().replace(new RegExp("-", "g"), "").toLocaleLowerCase()
      let token = uuid().replace(new RegExp("-", "g"), "").toLocaleLowerCase()
      // ctx.tickets[tk] = userInfo.username
      ctx.session[token] = tk
      ctx.cookies.set('token', token, {
        maxAge: 60 * 60 * 1000, //有效时间,单位毫秒
        httpOnly: false,
        path: '/',
      });
      ctx.redirect(`${ctx.query.redirectUrl}?token=${tk}`)
    } else {
      //todo 登录失败
      console.log('登陆失败')
    }
  })
  router.get('/check_token', async (ctx, next) => {
    const token=ctx.query.token;
  const result={
      error:0,
      userId:'admin'
  }

  ctx.body=result
   
  })
module.exports = router

登录页面

<!DOCTYPE html>
<html lang="en">

<head>
    <title></title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
    <div>
        欢迎登陆
        <div>
            用户名:<input type="text" id="name"/>
        </div>
        <div>
            密码:<input type="text" id="password"/>
        </div>
        <button onclick="login()">登陆</button>
    </div>
</body>
<script>
    function login() {
        
        var xhr = new XMLHttpRequest();
        var data = {
            username:document.getElementById('name').value,
            password:document.getElementById('password').value
        }
        
        xhr.open('post', "/login"+window.location.search, true);
        xhr.setRequestHeader('Content-Type','application/json;charset=UTF-8')
        xhr.send(JSON.stringify(data));
        
        xhr.onreadystatechange = function () {
            // 这步为判断服务器是否正确响应
            if (xhr.readyState == 4 && xhr.status == 200) {
                console.log(xhr.responseText);
            }
        };
    }
</script>

</html>

app1、app2代码

const Koa=require('koa');
const Router=require('koa-router')

const static=require('koa-static')
const path=require('path');
const app=new Koa();
const router=new Router();
const cors = require('koa2-cors');
const session=require('koa-session')
const  koa2Req=require('koa2-request');

app.keys=['key']

const keyMap={
  '8686':'koa:sess8686',
  '8787':'koa:sess8787'
}
const CONFIG={
    key:keyMap[process.env.PORT] || 'koa:sess',
    maxAge:1000*60*60*24,
    httpOnly:true
}
app.use(session(CONFIG,app))
app.use(cors({
    // 任何地址都可以访问
    origin:"*",
    // 指定地址才可以访问
    // origin: 'http://localhost:8080',
    maxAge: 2592000,
    // 必要配置
    credentials: true
  }));
const system=process.env.SERVER_NAME

router.get("/",async (ctx)=>{
    let user=ctx.session.user
    console.log(`user in session`,ctx.session)
    if(user){
       ctx.body = '登录成功'
    }else{
      let token=ctx.query.token
      if(!token)//第一次不带token进来需要去登录页面
      {
          ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.origin}`)
      }
      else //第二次进入的时候走这一步 发请求给认证服务器验证token
      {
        //ajax请求
        const url=`://localhost:8383/check_token?token=${token}&t=${new Date().getTime()}`
        let data = await koa2Req(ctx.protocol + url);
        console.log(data.body)
        if(data && data.body){
            try {
                const body=JSON.parse(data.body)
                const {error,userId}=body;

                console.log(error,userId)
                if(error==0){
                    if(!userId){
                        ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.origin}`)
                        return
                    }
                    ctx.session.user=userId;
                    ctx.body ='登录成功'
                }else{
                    ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.origin}`)
                }
            } catch (error) {
                // ctx.redirect(`http://localhost:8383/login?redirectUrl=${ctx.host+ctx.originalUrl}`)   
            }

            
        }
      }
    }
})
app.use(router.routes())


const port=process.env.PORT||8787

app.listen(port,()=>{
    console.log(`app ${system} running at ${port}`)

})