tiodot / tiodot.github.io

总结归纳--做的东西不是每一件都留到现在 但是能通过实践,收获了如何构建一套系统,一套工具的方法论
https://xchb.work
8 stars 0 forks source link

Nodejs单点登录系统之cookie版实现 #22

Open tiodot opened 7 years ago

tiodot commented 7 years ago

单点登录的三种实现方式中,介绍了三种的单点登录实现方式:

  1. 以Cookie作为凭证媒介
  2. 通过JSONP实现
  3. 通过页面重定向的方式

文章讲的思路很清楚,就差使用代码实现,因而先实现以Cookie作为凭证媒介这种方式,这里使用node+koa2实现。Cookie相关的知识可以参考 #21。

以Cookie作为凭证媒介的思路为: cookie 来源:单点登录的三种实现方式

这里的父应用就是需要实现的单点登录系统,而子应用就是实际的业务系统之类。 要实现单点登录系统,至少需要:

  1. 实现登录功能
  2. 实现退出功能
  3. 提供验证Cookie接口

1. 登录功能

提供登录页面,输入帐号密码,然后通过ajax请求验证帐号密码的正确性。如果正确则返回需要跳转的地址,并注入相关cookie,后端实现为:

router.post('/login', async (ctx) => {
    const data = ctx.request.body;
    if (!isValid(data)) { // 验证帐号密码是否正确
        return ctx.body = {code: 1, msg: '帐号或者密码不正确'};
    }
    // 对用户名进行加密作为登录凭证。
    ctx.cookies.set('token', cryptoCookie.encrypt(data.name), {
        domain: 'local.com' // 设置域名为顶级域名
    });
    ctx.body = {code: 0, msg: '登录成功', url: '/detail'} // 验证成功返回需要跳转的url
});

前端请求验证帐号密码是否正确:

window.fetch('/login', {
    method: 'post',
    headers: {
        'Accept': 'application/json, text/plain, */*',
        'Content-Type': 'application/json'
    },
    credentials: 'same-origin', // 需要设置,否则设置不上cookie
    body: data
}).then(res => res.json()).then(res => {
     if (res.code !== 0) {
            $error.innerHTML = res.msg;
     } else {
            window.location.href = res.url;
     }
})

2. 退出功能

因为登录凭证是通过cookie中的token字段来标识的,所以退出只需要清空一下相关cookie就行:

router.get('/logout', async (ctx) => {
    ctx.cookies.set('token', '', {
        domain: 'local.com',
        expires: new Date('1970-1-1') // 设置过期时间,由于该时间小于当前时间,浏览器会自动清除该cookie
    });
    await ctx.redirect('/login'); 
});

3. 提供验证Cookie的接口

因为登录凭证都是通过cookie中的token存储的。所以需要取出token中的存储的信息,解密成用户民,然后判断用户民是否存在。

router.get('/validate', async (ctx) => {
    const token = ctx.cookies.get('token');
    const name = cryptoCookie.decrypt(token);
    if (isExist(name)) {
        ctx.body = {
            code: 0,
            msg: 'success',
            name: name
        }
    } else {
        ctx.body = {
            code: 1,
            msg: 'error'
        }
    }
});

在子应用中使用

在子应用中,为了简单,每次请求都向登录系统发请求验证是否已经登录,如果没有登录则重定向到父应用的登录页面进行登录:

app.use(async (ctx, next) => {
    const cookie = ctx.cookies.get('token');
    if (!cookie) {
        ctx.state.isLogin = false;
    }
    else {
        await new Promise((resolve, reject) => {
            http.request({
                host: 'local.com',
                port: 3000,
                path: '/validate',
                method: 'get',
                headers: {
                    'Cookie': ctx.request.headers.cookie
                }
            }, (res) => {
                res.setEncoding('utf8');
                let data;
                res.on('data', (chunk) => {
                    data = JSON.parse(chunk);
                });
                res.on('end', () => {
                    resolve(data);
                });
            }).end();
        }).then((res) => {
            if (res.code !== 0) {
                ctx.state.isLogin = false;
            }
            else {
                ctx.state.isLogin = true;
                ctx.state.name = res.name;
            }
        });
    }
    await next();
});

这里有个遗留问题,每次请求验证Cookie是否可以做些优化,如果第一次验证成功,则隔一段时间再去验证cookie,以减少请求次数。

参考

  1. 以上实现源码
  2. 单点登录的三种实现方式