关于一个用过的 sso 项目:
这个项目因为是第一次接触,以为 sso 项目就是这个样子:
使用框架技术:
springMVC,mybitas,redis,dubbo,zookerper
主要功能:
用户登录 /注册(第三方登录 /注册),用户注销,用户验证(用于分布式切面类)
前两个功能,就不详细说了“注册和登录时一个接口,就是查询数据库中是否有第三方的 id,数据库没有就生成三个表( three_id ),( user_id )-(account_id),( id,token )”
重点说第三个:用户验证,用户验证是提供给其他服务消费方用的,项目中使用的是 dubbo 分布式,所以就把验证写成接口形式。需要调用的服务( controller )中,写了一个 aop,验证用户是否登录,就是调用这个代码,然后继续执行,否者就返回异常。
因为是数据库查询慢,所以又加了一个缓存,就是吧验证信息,和用户信息放在了 redis 一份。
公司是做移动端对接的,一开始设计并没有包含 web 端,后来 web 端也是我写的,接口没动,只是把 token 在 web 登录之后手动保存在客户端,不同域名下访问做了 nginx 的接口过滤。
为什么要拿出来说一下呢,是因为去面试他让我说 sso 单点的 cookie,他说服务端怎么放 cookie,我说服务端放 redis,不管客户端 cookie 怎么放,不只是误解,还是理解不对,面试官对此回答不满意,所以我就注意到了这个事情,所以之后就看一些文章:
1、单台的 web 是用 session 的,客户端和服务端的 session 是共享的,所以 jsp 和服务端中读写 session 可以有效验证(都用过)
2、到分布式的时候,遇到多服务器 session 不一致,可以用 tomcat 的广播组同步 session,也可以解决(没有验证)
3、就是设置项目中的 redissession,这个是 spring 的一个组件(我统称为 spring 的技术为组件,不知道有没有什么问题),具体没用过,内部 redis 的 session 管理机制可以理解
起初我是觉得我的 sso 项目是第三者模式。可是,偶尔跟同学(也是同行),他说他用的就是第三者模式,但是会用到 session,会有一个用户会话的东西,第一次验证之后,建立 session,以后就不用验证了。
问题来了: 在我 debug 的时候,而我的项目是每次都过验证的( aop ),也就是说每次都用验证,所以,好像我的是一个假的单点登录,而是每次的令牌验证?
所以,我想问一下大牛们,sso 单点登录项目,原理和最完善的原理模式是什么,我司的这个 sso 项目,是不是一个真正意义上的单点登录。
顺便献上 aop 验证的那段代码:
public String checkToken(ProceedingJoinPoint pjp, CheckToken checkToken) throws Throwable {
// 获取 request 属性
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
try {
// 从 header 中获取要校验的参数
CheckUserParam param = new CheckUserParam();
RequestHeaderToolkit.createSsoParam(request, param);
// 打印入参
if(log.isInfoEnabled())
log.info("CheckTokenAspect/checkToken,parram={}", JSONObject.toJSONString(param));
// 先从缓存中取出合适的数据 TODO
// 缓存中没有,调用 dubbo 接口
CheckUserResult rtn = ssoInterface.checkUser(param);
// 返回有异常,直接抛出异常
if(rtn.getRetCode() != 0) {
log.error("校验用户,发生异常: parram={},rtn="+rtn.getRetCode() ,JSONObject.toJSONString(param));
Map<String, Object> map=new HashMap<String, Object>();
map.put("retCode", rtn.getRetCode());
map.put("msg", rtn.getMsg());
return JSON.toJSONString(map);
}
// 如果不允许匿名登录,则抛出异常
if(!checkToken.allowGuest() && ( StringUtils.isBlank(rtn.getUserRole()) ||
RoleEnum.GUEST.name().equals(rtn.getUserRole()))) {
log.error("不允许匿名登录,parram={}" ,JSONObject.toJSONString(param));
Map<String, Object> map=new HashMap<String, Object>();
map.put("retCode", WirelessErrorEnum.PLEASE_LOGIN_PARAM_ERROR.getCode());
map.put("msg", WirelessErrorEnum.PLEASE_LOGIN_PARAM_ERROR.getMessage());
return JSON.toJSONString(map);
}
// 构造返回值
request.setAttribute(StaticFinalAppId.userId, String.valueOf(rtn.getUserId())); // 登录用户 id 未登录用不送或送 0
request.setAttribute(StaticFinalAppId.userRole, rtn.getUserRole()); // 登录用户 id 未登录用不送或送 0
request.setAttribute(StaticFinalAppId.chatPassword, rtn.getChatPassword()); // 登录聊天室时使用的密码
// 继续执行方法
return (String)pjp.proceed();
} catch (RuntimeException e) {
log.error("校验用户信息时,发生异常," + e.getMessage() ,e);
Map<String, Object> map=new HashMap<String, Object>();
map.put("retCode", -1);
map.put("msg", "An error occurred, please contact customer service.(-1)");
return JSON.toJSONString(map);
}
}