我们常用 SecurityUtils.getSubject().getPrincipal();获取当前登录用户信息,但是这个方法是如何获得用户信息的?Shiro又是如何区分不同用户的身份的?
问题1. SecurityUtils.getSubject().getPrincipal()返回类型;
查看源码得知它是Object,但是实际上,他的返回类型由我们控制。
在Realm类中有个doGetAuthenticationInfo方法,我们常在这里进行登录逻辑处理,其返回类型是AuthenticationInfo
,我们通常使用SimpleAuthenticationInfo
,追进去可以看到其第一个参数就是principal,即我们的用户类型:
因此,如果我们最终
User user = new User();
user.setPassword("111");
user.setName("sxuer");
return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
则SecurityUtils.getSubject().getPrincipal()将得到User类。
若
String user = "sxuer";
return new SimpleAuthenticationInfo(user, "123", user);
则SecurityUtils.getSubject().getPrincipal()将得到字符串。
问题2. Shiro如何利用session保持会话
先说结果:
- 若使用Shiro默认的session会话管理:用户登录–>创建session–>创建cookie–>用户二次访问–>从cookie中读取sessionId–>根据sessionId取得用户身份
- 若使用自定义的session会话管理,适用于前后分离(但需要解决跨域cookie禁止问题):用户登录,同时携带
sId
–>检查到sId参数,使用自定义getsessionId方法–>保存sessionId到cookie中–>用户二次访问,携带sessionId–>根据sessionId取得用户身份
上述两者的主要区别在于,第二种用户登录时,需要携带一个额外的参数,用于创建后续访问的sessionId。
原理
很容易得知,Shiro中有个会话管理器DefaultWebSessionManager
,既然Shiro使用session保持会话,那么核心就在于session的创建以及获取(校验)。
在DefaultWebSessionManager
中有个getSessionId
方法,向上追溯,可以发现Shiro会从request中查询cookie,如果找到了,那就作为sessionId保存–>可以得出,sessionId其实是由客户端控制的。如果从cookie中没找到,就从uri中读取JSESSIONID
参数,如果依旧没有,就会抛出找不到的异常。
我们可以重写getSessionId
方法,从而实现session的定制(若将cache换成redis,就跟传统上使用redis保存token的原理基本一致)。
假设重写的getSessionId如下:
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// 如果参数中包含“__sid”参数,则使用此sid会话。 例如:http://localhost/project?__sid=xxx&__cookie=true
String sid = request.getParameter("__sid");
if (StringUtils.isNotBlank(sid)) {
// 是否将sid保存到cookie,浏览器模式下使用此参数。
if (WebUtils.isTrue(request, "__cookie")) {
HttpServletRequest rq = (HttpServletRequest) request;
HttpServletResponse rs = (HttpServletResponse) response;
Cookie template = getSessionIdCookie();
Cookie cookie = new SimpleCookie(template);
cookie.setValue(sid);
cookie.saveTo(rq, rs);
}
// 设置当前session状态
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.URL_SESSION_ID_SOURCE); // session来源与url
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sid);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sid;
} else {
return super.getSessionId(request, response);
}
}
当用户登录时,携带了参数__sid=1,那么Shiro会利用该参数创建session,但是并非该值就是session。
Shiro中有个类叫做SessionDao
,显然是用于创建Session的,我们进入查看,会发现有个doCreate,阅读之后可以发现,他会利用sessionIdGenerator
生成sessionId,然后使用assignSessionId(session, sessionId)
将sessionId设置到session对象中。然后在Cache类中(如CachingSessionDao
)以sessionId:session作为键值对保存。
获取sessionId:
从cookie中获取session
若为空,则创建session,保存到cookie中,保存到本地Cache中
相关方法:
getSessionId(ServletRequest request, ServletResponse response)
getSessionIdCookieValue(request, response)
readValue(HttpServletRequest request, HttpServletResponse ignored)
getCookie(request, name)
cookie.getValue();
request.setAttribute