- 背景介绍
单点登录SSO(Single Sign On),就是在多系统环境下,用户在其中一个系统登录后,就不用在其它系统再登录了。
早期我们的web系统都是单体应用,所有功能都写到一个war包中,用户登录认证功能处理起来相对比较简单。用户登陆成功后,服务器将用户信息写入到会话中(Session)。会生成 session id来标记这块内存区间是属于你的,并且,这个 session id( jsessionid ) 会写入到你的浏览器 cookie 中,只要你浏览器没关闭,每次向服务器发送请求,服务器就会从你发送过来的 cookie 中去取这个 session id,然后根据这个 session id 到相应的内存中取出你之前存放的数据,但是,如果退出登录。服务器会清除属于你的内存区域,再登录时,重新生成新的 session。
但是随着应用的访问量不断扩大,单体应用无法满足应用要求,由此分布式应用应运而生。在分布式应用环境下,当某个子系统访问量增加时,我们会把这个系统同时布署多份。当同一个用户登录成功后,由于Nignx反向代理策略不能保存让这个用户访问到同一个子系统,也就是用户登录成功的session是不能在多个子系统中共享的,从而会导致其登录失败。所以在分布式应用中要解决session的同步问题,这就是单点登录的由来。
2. 解决方案:基于kisso的身份认证方案
kisso = cookie sso 基于 Cookie 的 SSO 中间件,它是一把快速开发 java Web 登录系统(SSO)的瑞士军刀。另外Kisso是基于JWT规范的单点登录中间件。
开源地址:https://gitee.com/baomidou/kisso.git
### 代码示例 ###
SSOToken ssoToken = SSOToken.create();
ssoToken.setIp(request).setId("1001").setIssuer("admin");
int cookieMaxage = SSOConfig.getInstance().getCookieMaxage();
ssoToken.setTime(new Date().getTime()+cookieMaxage*1000);//设置过期时间
// cookie模式登录,写入加密token值
SSOHelper.setCookie(request, response, ssoToken, true);
// 生成 jwt 票据,访问请求头设置‘ accessToken=票据内容 ’
String jwtToken = ssoToken.getToken();
// 解析票据
SSOToken ssoToken = SSOToken.parser(jwtToken);
// 获取登录用户id, 登录账号
ssoToken = SSOHelper.getSSOToken(request);
String userId = ssoToken.getId();
String loginName = ssoToken.getIssuer();
// 退出登录(清除cookie对应的token信息)
SSOHelper.logout(request, response);
// 多端登录,调用踢人下线,需要实现SSOCache接口
SSOHelper.kickLogin(userId);
// 权限拦截器类 SSOSpringInterceptor
// 注解不拦截 @Login(action = Action.Skip)
// yml 配置 kisso.config....
kisso:
config:
signkey: xxxx #对称签名密钥(非必须)
cookieName: accessToken #COOKIE key名称
cookieDomain: www.domain.com # COOKIE域名
3. 解决方案:基于Sa-Token单点登录+权限认证框架
如果你的项目属于后台管理系统或者有权限认证相关需求,推荐另一个开源项目:Sa-Token,它是一个轻量级Java权限认证框架;
主要解决:登录认证、权限认证、Session会话、单点登录、OAuth2.0 等一系列权限相关问题。
相对于传统老牌框架:Shiro、Spring Security集成时需要自定义Realm,写全局过滤器,写各种配置文件;sa-token的API设计超简单!
开源地址:https://gitee.com/dromara/sa-token.git 核心功能举例如下:
- 登录验证 —— 轻松登录鉴权,并提供五种细分场景值
- 权限验证 —— 适配RBAC权限模型,不同角色不同授权
- Session会话 —— 专业的数据缓存中心
- 踢人下线 —— 将违规用户立刻清退下线
- 分布式会话 —— 提供jwt集成和共享数据中心两种分布式会话方案
- 微服务网关鉴权 —— 适配Gateway、Soul、Zuul等常见网关组件的请求拦截认证
- 单点登录 —— 一处登录,处处通行
- 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
### 代码示例 ###
// 在登录时写入当前会话的账号id
StpUtil.setLoginId(10001);
// 然后在任意需要校验登录处调用以下API
// 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
// 权限认证示例 (只有具有user:add权限的会话才可以进入请求)
@SaCheckPermission("user:add")
@RequestMapping("/user/insert")
public String insert(SysUser user) {
// ...
return "用户增加";
}
// 使账号id为10001的会话注销登录
StpUtil.logoutByLoginId(10001);
StpUtil.setLoginId(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.logoutByLoginId(10001); // 让账号为10001的会话注销登录(踢人下线)
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.setLoginId(10001, "PC"); // 指定设备标识登录
StpUtil.logoutByLoginId(10001, "PC"); // 指定设备标识进行强制注销 (不同端不受影响)
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号