记录shiro 的一次bug排除
用户在登陆成功之后,访问项目主页会话无效提示需要再次登陆
菜鸡解决问题第一招 gooogle
1,shiro是如何进行会话验证的?
并没有查到有效的信息,大都数都是说分析Shiro的源码,并没有说明到底前端与服务器交互,是如何判断此次会话有效,(因为着急解决bug的心理,也没细看,就想找到直接答案).因为对于Web 服务sesseion理解不深刻,
并不知道JSESSIONID 的存在(shiro在登陆时会在cookie中加入key为JSESSIONID 的唯一标识)。
session的工作原理
(1)当一个session第一次被启用时,一个唯一的标识被存储于本地的cookie中。
(2)服务端会保存此唯一标识,前端每次请求会在cookie里带上这唯一标识进行会话判断
菜鸡解决问题第二招 请教大佬
1,最后解决方案是在login接口的时候吧sessionid 通过JWT加密传给前端
2,重写DefaultWebSessionManager类 的getSessionId 方法
public class CustomSessionManager extends DefaultWebSessionManager {
private static final String AUTHORIZATION = "Authorization";
private static final String COOKIE = "Cookie";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
public CustomSessionManager() {
super();
}
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// 获取requetstHeader中 Authorization 的值
String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// 获取请求链接中 token 的值
if (StringUtils.isEmpty(id)) {
id = request.getParameter("token");
}
// 获取请求链接中Cookie中 Authorization 的值
if(StringUtils.isEmpty(id)){
HttpServletRequest httpRequest = WebUtils.toHttp(request);
Cookie[] cookies = httpRequest.getCookies();
if (cookies != null){
for (Cookie c: cookies) {
if (AUTHORIZATION.equals(c.getName())){
id = c.getValue();
}
}
}
}
//如果请求头中有 Authorization 则其值为sessionId
if (!StringUtils.isEmpty(id)){
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
try {
return JwtUtils.verifyToken(id).get("token").asString();
} catch (Exception ex) {
return super.getSessionId(request, response);
}
} else {
//否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
菜鸡决定要学习
重写之后虽然暂时能够解决这个bug,但是没有找到事情的原因啊(抓狂ing)
于是自己就尝试去看了Shiro官网的文档关于SessionManagement
Shiro 官网 强烈推荐大家用google翻译,以前总是不想用。靠自己蹩脚的英语水平去理解,最后也是是懂非懂,所以这里夸一下google翻译真心翻译的很棒。
最后找到JSESSIONID 这个关键点,发现是前端没有传这个值 。
但是我不甘心啊。于是决定去一探Shiro的源码。概念都在官网上,这里盘代码,
就从这三句代码开始
// subject 是与当前线程进行绑定的,简单理解就是用户,但是官方说的是用户视图(包含用户的权限,角色),
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken =
new UsernamePasswordToken(user.getName(), password);subject.login(usernamePasswordToken);
直接进login方法然后一步步进去,这里就不多加剩余的代码,直接撸到什么地方生成JSESSIONID ,什么时候
加到Cookie中。
- 服务端生成sessionId并保存
// 类路径
org\crazycake\shiro-redis\3.0.0\shiro-redis-3.0.0.jar!\org\crazycake\shiro\RedisSessionDAO.class
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Override
protected Serializable doCreate(Session session) {
if (session == null) {
logger.error("session is null");
throw new UnknownSessionException("session is null");
}
// 服务器段保存改sessionId 保存在redis中
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}
- Session 管理器设置Cookie名字
// org.apache.shiro.web.session.mgt.DefaultWebSessionManager
public DefaultWebSessionManager() {
// JSESSIONID
Cookie cookie = new SimpleCookie(ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
cookie.setHttpOnly(true); //more secure, protects against XSS attacks
this.sessionIdCookie = cookie;
this.sessionIdCookieEnabled = true;
this.sessionIdUrlRewritingEnabled = true;
}
- 将sessionId 设置到request Cookie 中
/** org.apache.shiro.session.mgt.eis.AbstractSessionDAO **/
protected void assignSessionId(Session session, Serializable sessionId) {
((SimpleSession) session).setId(sessionId);
}
//org.apache.shiro.web.session.mgt.DefaultWebSessionManager
@Override
protected void onStart(Session session, SessionContext context) {
super.onStart(session, context);
if (!WebUtils.isHttp(context)) {
log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response " +
"pair. No session ID cookie will be set.");
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
if (isSessionIdCookieEnabled()) {
Serializable sessionId = session.getId();
// 下面那个方法
storeSessionId(sessionId, request, response);
} else {
log.debug("Session ID cookie is disabled. No cookie has been set for new session with id {}", session.getId());
}
request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
}
// 保存sessionid
private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
if (currentId == null) {
String msg = "sessionId cannot be null when persisting for subsequent requests.";
throw new IllegalArgumentException(msg);
}
Cookie template = getSessionIdCookie();
Cookie cookie = new SimpleCookie(template);
String idString = currentId.toString();
cookie.setValue(idString);
cookie.saveTo(request, response);
log.trace("Set session ID cookie for session with id {}", idString);
}