1、问题:
shiro框架的授权依赖于shiro内置的session,这意味着如果客户端禁用掉cookie的话,shiro无法通过sessionId来获取内置session中的内容,也就无法实现权限管理方面的操作。
2、解决思路:
shiro的session管理核心在于org.apache.shiro.web.session.mgt.DefaultWebSessionManager这个类,其中的getSessionId()方法是shiro获取sessionId的方法,shiro通过获取到的sessionId来获取内置的seesion,而当客户端禁用cookie的时候,shiro是无法通过改方法获取到sessionId的,原因如下:
因此,解决本问题的主要在于让shiro获取到sessionId,进而获取到内置的session。
我们可以通过重写getSessionId()方法的形式,来使shiro按照我们自己的方式获取sessionId,而非通过cookie获取sessionId,一般情况下,都是让前端在请求头中携带一个token,我们在重写getSessionId()方法的过程中,获取请求头中的token,以此改变shiro获取sessionId的形式。
而前端传递的token则由后端调用SecurityUtils.getSubject().getSession().getId()方法,获取shiro中的sessionId,然后以响应体或者是响应头的形式返回给前端。这样shiro就可以根据前端传递的token找到每个sessionId对应的session,进行权限认证操作。
3、解决步骤:
3.1、配置shiro个安全管理器(也有叫shiro过滤器的)
shiro的基本配置这里就不多描述了,自行百度下。
3.2、重写getSessionID()方法
public class ShiroSessionManager extends DefaultWebSessionManager {
/**
* 返回客户端的,
*/
public final String TOKEN_NAME = "memberToken";
/**
* 这个是客户端请求给服务端带的header
*/
public final static String HEADER_TOKEN_NAME = "memberToken";
public final static Logger log = LoggerFactory.getLogger(ShiroSessionManager.class);
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
/**
* 重写getSessionId,分析请求头中的指定参数,做用户凭证sessionId
*/
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response){
String sessionId = WebUtils.toHttp(request).getHeader(HEADER_TOKEN_NAME);
System.out.println("获取的sessionId为"+sessionId);
if(StringUtils.isEmpty(sessionId)){
return super.getSessionId(request, response);
}else{
//如果请求头中有 memberToken 则其值为sessionId
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,sessionId);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
return sessionId;
}
}
@Override
protected void onStart(Session session, SessionContext context) {
System.out.println("执行onStart");
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.");
} else {
HttpServletRequest request = WebUtils.getHttpRequest(context);
HttpServletResponse response = WebUtils.getHttpResponse(context);
Serializable sessionId = session.getId();
this.storeSessionId(sessionId, request, response);
request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
}
}
/**
* 把sessionId 放入 response header 中
* onStart时调用
* 没有sessionid时 会产生sessionid 并放入 response header中
*/
private void storeSessionId(Serializable currentId, HttpServletRequest ignored, HttpServletResponse response) {
if (currentId == null) {
String msg = "sessionId cannot be null when persisting for subsequent requests.";
throw new IllegalArgumentException(msg);
} else {
String idString = currentId.toString();
response.setHeader(this.TOKEN_NAME, idString);
log.info("Set session ID header for session with id {}", idString);
log.trace("Set session ID header for session with id {}", idString);
}
}
3.3、将重写过后的类加到到安全管理器中
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm());
securityManager.setSessionManager(defaultWebSessionManager());
return securityManager;
}
@Bean
public DefaultWebSessionManager defaultWebSessionManager(){
System.out.println("注入新的sessionManager");
ShiroSessionManager manager = new ShiroSessionManager();
return manager;
}
3.4、将sessionId返回给前端
//获取使用者对象
Subject subject = SecurityUtils.getSubject();
//封装用户信息
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(memberName, memberPwd);
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
//此时返回给用户一个sessionId即可,用户的信息在在shiro进行授权的时候进行操作(需要获取用户信息的必须要先进行授权)
String sessionId = (String) SecurityUtils.getSubject().getSession().getId();
Map<String, Object> resMap = new HashMap<>();
resMap.put("memberToken", sessionId);
System.out.println("用户登录时的token:" + sessionId);
return AjaxResult.success("登录成功", resMap);
这里是将sessionId放在响应体中,如果想要放在响应头中则需要重写onStart()与storeSessionId()方法,操作详见3.2
4、其他
shiro的授权只会在第一次需要权限认证的时候进行,授权过后就不会再次授权,因此开启运行时编译的小伙伴需要注意下。其次,我这边把用户信息填充方面的操作统统放入授权中,觉得既然需要用户信息了,那就必须要授权,总感觉有点问题,但是又说不上来哪里出问题了,求指点