假设用户 X 需同时登录站点 A 和站点 B,这两个站点之间其实是有关联性的,但是如果用户认证数据不通用,那将需要注册或登录两次。单点登录系统(Single Sign On,简称 SSO)就是为了解决这种场景的问题,建立一种用户认证中心,只要经过这个中心注册或登录了某一站点服务的用户,总是能够认证登录这个中心所授权的其他所有服务。
CAS (Central Authentication Service) 是耶鲁 Yale 大学发起的一个 java 开源项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方案( Web SSO ),github 地址。CAS 作为一种单点登录框架,后端可配置不同的用户数据库,支持自定义验证或加密逻辑,并提供不同的协议用于与业务 server(cas-client)间的通信。CAS 的源码是由 java 写的,因此对于 java 的 web 项目天生友好。当然只要实现 CAS 相关协议的 client,无论是哪种语言实现的,都能集成到 CAS 认证框架中。CAS 的部署请参考这里从结构上看, CAS 包含两个部分: CAS Server 和 CAS Client 。
首先介绍一下项目架构,项目前端采用 Vue 开发,服务端采用 node + express 开发。项目最终上线部署时,前端打包后放在服务端的 /public
目录下,即采用 express 静态服务功能。开始我还是固有的思维,考虑在前端实现单点登录的集承,用户登录不都是在前端做的吗,调用服务端接口,CAS 也提供了一些列的 Restful Api,然而当我去实现的时候却相当麻烦,还会有跨域的问题。然后想到了可否直接在服务端控制访问权限?于是网上搜索一番,终于找到了 connect-cas2 node 中间件,其实它就是 CAS Client 的 node 实现,正好符合我的项目场景。connect-cas2 的使用也比较简单,具体请看官方文档,直接抄文档示例就可以了。以下是主要代码:
var express = require('express') var ConnectCas = require('connect-cas2') var bodyParser = require('body-parser') var session = require('express-session') var cookieParser = require('cookie-parser') var MemoryStore = require('session-memory-store')(session) var app = express() app.use(cookieParser()) app.use( session({ name: 'NSESSIONID', secret: 'Hello I am a long long long secret', resave: true, saveUninitialized: true, store: new MemoryStore() }) ) var casClient = new ConnectCas({ debug: true, ignore: [/\\/ignore/], match: [], servicePrefix: '<http://localhost:3000>', serverPath: '<http://your-cas-server.com>', paths: { validate: 'cas/validate', serviceValidate: 'cas/serviceValidate', login: 'cas/login', logout: 'cas/logout', proxy: '', proxyCallback: '' } }) app.use(casClient.core()) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: true })) app.get('/logout', casClient.logout()) app.get('/', function(req, res) { if (!req.session.cas.user) { return next() } const username = req.session.cas.user return res.send( '<p>You are logged in. Your username is ' + username + '. <a href="/logout">Log Out</a></p>' ) }) app.get('/api', function(req, res) { return res.send('hi') }) app.listen(3000)