客户端禁用cookie时如何使用shiro框架

  • Post author:
  • Post category:其他


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的授权只会在第一次需要权限认证的时候进行,授权过后就不会再次授权,因此开启运行时编译的小伙伴需要注意下。其次,我这边把用户信息填充方面的操作统统放入授权中,觉得既然需要用户信息了,那就必须要授权,总感觉有点问题,但是又说不上来哪里出问题了,求指点



版权声明:本文为m0_54096780原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。