关于对shiro跨域请求认证的解决方案

大家在写项目时可能会遇到前后端分离的情况,那么这个时候就会遇到跨域请求的问题。我们知道在web环境下http是一种无状态的通讯协议,要想记录和校验用户的登录状态必须通过session的机制来实现,浏览器是通过cookie中存储的sessionid来确定用户的session数据的,shiro默认也是采用这种机制。

首先简要说一下怎样突破shiro跨域访问的限制,由于浏览器在访问后台服务前,会先发priflight请求(method=options),这时仍然会遇到跨域问题,为了解决priflight的请求问题,我们需要定义一个filter,对priflight的请求直接返回200,表示服务器允许priflight请求;对应的filter如下:

@Order(-100)
@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/*",filterName = "shiroLoginFilter")
public class ShiroLoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // 允许哪些Origin发起跨域请求
        //String orgin = request.getHeader("Origin");
        // response.setHeader( "Access-Control-Allow-Origin", config.getInitParameter( "AccessControlAllowOrigin" ) );
        response.setHeader( "Access-Control-Allow-Origin", "*" );
        // 允许请求的方法
        response.setHeader( "Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT" );
        //多少秒内,不需要再发送预检验请求,可以缓存该结果
        response.setHeader( "Access-Control-Max-Age", "3600" );
        // 表明它允许跨域请求包含xxx头
        response.setHeader( "Access-Control-Allow-Headers", "*" );
        //允许暴露给客户端的响应头
        response.setHeader("Access-Control-Expose-Headers","token");
        //prefight请求
        if (request.getMethod().equals( "OPTIONS" )) {
            response.setStatus( 200 );
            return;
        }
        chain.doFilter( servletRequest, response );
    }

    @Override
    public void destroy() {

    }
}

在这里尤其要注意一下设置24行response.setHeader( “Access-Control-Allow-Headers”, “*” );和26行response.setHeader(“Access-Control-Expose-Headers”,“token”);(设置token可以被js获取方便后续每次请求携带)的必要性,到这边跨域问题算是解决了,紧接着讨论跨域请求之后shiro的登录验证问题。

遇到的问题:
对于同源请求,浏览器是允许js访问cookie并在请求时携带的,但是对于来自h5应用或者小程序的跨域请求,浏览器的同源策略不允许js访问跨域的cookie,这样每次请求shiro获取的cookie都为空,自然就无法通过shiro的登录校验。对此问题,首先想到的改变登录时的校验方式,即自定义token对来自h5应用或者小程序的请求进行区别校验,下面给出具体方案及示例代码。

token身份鉴权的流程(方法一)

1.服务端在用户正常登录之后,通过特定算法生成一个全局唯一的字符串token设置到响应头中返回给客户端。
2.客户端在接下来的请求都会在请求头中携带token,服务端拦截token并对用户做身份鉴权。
3.token带有自动失效的机制,当用户主动退出或者失效时间一到则服务端删除该会话信息。

首先自定义过滤器(继承自shiro的FormAuthenticationFilter)并覆写它的isAccessAllowed方法,此方法返回值若为true则说明shiro鉴权通过,否则执行重写的redirectToLogin方法跳转到登录页面或者是返回具体错误原因,以下是示列代码:

public class MyFormAuthenticationFilter extends FormAuthenticationFilter {

    @Autowired
    private RedisManager redisManager;

    @Override
    protected boolean isAccessAllowed(ServletRequest request,
                                      ServletResponse response, Object mappedValue) {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        boolean isLogin;
        String device = httpRequest.getHeader("device");
        // 如果是客户端是H5或者小程序
        if (StringUtils.isNotBlank(device) && device.equals("H5")) {
            String h5Token = httpRequest.getHeader("token");
            Cookie[] cookies = httpRequest.getCookies();
            if (null != cookies) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals("token")) {
                        cookie.setValue(h5Token);
                    }
                }
            }
            isLogin = isH5Login(h5Token);//绕过shiro,直接到redis中校验token
        } else {
            // 如果是APP或者PC端,通过shiro本身进行验证
            Subject subject = getSubject(request, response);
            isLogin = subject.isAuthenticated();
        }
        return isLogin;
    }

    public boolean isH5Login(String h5Token){
       return new RedisCache(redisManager).get(h5Token) != null;
    }
}

 	@Override
    protected void redirectToLogin(ServletRequest request,
                                   ServletResponse response) throws IOException {
        String loginUrl = getLoginUrl();
        if (logger.isDebugEnabled()) {
            logger.debug("客户端登录的URL:{}", loginUrl);
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        //System.out.println(httpRequest.getRequestURL());
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("text/html; charset=utf-8");
        httpRequest.setCharacterEncoding("UTF-8");
        // 是否为H5登录请求
        if (StringUtils.isNotBlank(httpRequest.getHeader("device"))
                && httpRequest.getHeader("device").equals("H5")) {
            String token = httpRequest.getHeader("token");
            if (logger.isDebugEnabled()) {
                logger.debug("客户端设备:{},token:{}", httpRequest.getHeader("device"), token);
            }
            if (StringUtils.isBlank(token)) {
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                httpResponse.getWriter().append(R.error("token不存在!").toString());
                httpResponse.getWriter().flush();
                httpResponse.getWriter().close();
            } else {
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                httpResponse.getWriter().append(R.error("认证失败!").toString());
                httpResponse.getWriter().flush();
                httpResponse.getWriter().close();
            }
        } else {
            // PC跳转 如果是非Ajax请求 按默认的配置跳转到登录页面
            if (!"XMLHttpRequest".equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {// 不是ajax请求
                WebUtils.issueRedirect(request, response, loginUrl);
            } else {
                // 如果是Aajx请求,则返回会话失效的JSON信息
                httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
                httpResponse.getWriter().append(R.error("请求失败!").toString());
                httpResponse.getWriter().flush();
                httpResponse.getWriter().close();
            }
        }
    }

下面是登录代码:

 @Log("登录")
 @PostMapping("/login")
 @ResponseBody
 void login(String username, String password, HttpServletRequest request, HttpServletResponse response) throws IOException {

     password = MD5Utils.encrypt(username, password);
     UsernamePasswordToken token = new UsernamePasswordToken(username, password);
     Subject subject = SecurityUtils.getSubject();
     PrintWriter out = null;
     try {
         subject.login(token);
         out = response.getWriter();
         if(request.getHeader("device").equals("H5")){
             String h5Token = UUID.randomUUID().toString();
             new RedisCache(redisManager).put(h5Token,"H5"+username);
             response.setStatus(HttpServletResponse.SC_OK);
             response.setHeader("token", h5Token);
             out.print(R.ok());
             out.flush();
         }else{
             response.setStatus(HttpServletResponse.SC_FORBIDDEN);
             out.print(R.error("非法请求"));
             out.flush();
         }
     } catch (AuthenticationException e) {
         response.setStatus(HttpServletResponse.SC_FORBIDDEN);
         out = response.getWriter();
         out.print(R.error("用户或密码错误"));
         out.flush();
     } finally {
         if (out != null) {
             out.close();
         }

 }

最后在前端代码中对xhr请求加上区分请求源的请求头:

<script th:inline="javascript">
        $.ajaxSetup({
        headers: {
            "device": "H5"
        }
    })
    </script>

在登录方法中,我是将登录之后生成的token设置为key存到redis并设置到响应头中,在此可以设置token在redis中的有效时间(我这里没有设置),isH5Login方法就是从redis中读取客户端传过来的token是否存在来校验是否通过,如果是同源请求则走shiro本身的校验机制。到这里基本上shiro的登录校验是绕过去了,其实这里并不是真的绕过,因为shiro该做的事情还是会照做,只不过是我们再到redis中去匹配一次而已,但是却带来了一个新的问题,那就是服务端通过SecurityUtils.getSubject().getSession();取到的用户session对象与之前登录时产生的session对象并不是同一个,原因是shiro本身在执行校验时由于无法获取到cookie中的token,所以它把这个请求当成是一个新的请求,每次调用都会创建一个新的session,但这个新session里面并不存在我们需要的用户相关登录信息。虽然此方法不失为一个解决方案,但是总觉得整个过程下来,代码和功能的实现都让人觉得很别扭,因此本文想在不改变整体的shiro校验机制情况下从源码的角度去逐步剖析shiro是如何拦截未登录请求的,从根源上来寻求解决方案,同时又不会对已对接好的业务接口造成影响,在提出方案之前下面先介绍另一种简单有效的方案。

跨域请求携带cookie(方法二)

那么如何让h5应用也能在跨域的情况下传输cookie呢?
首先服务端在使用cors协议时需要设置响应消息头Access-Control-Allow-Credentials的值为true即允许在ajax访问时携带cookie,客户端方面也需通过js设置withCredentials为true才能真正实现跨域传输cookie.另外为了安全,在cors标准里不允许Access-Control-Allow-Origin设置为*,而是必须指定明确的、与请求网页一致的域名.cookie也依然遵循“同源策略”,只有用目标服务器域名设置的cookie才会上传,而且使用document.cookie也无法读取目标服务器域名下的cookie。
接下来我们来看看代码是怎么实现的:

@Order(-100)
@Component
@ServletComponentScan
@WebFilter(urlPatterns = "/*",filterName = "shiroLoginFilter")
public class ShiroLoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String origin = request.getHeader("Origin");
        if(origin == null) {
            origin = request.getHeader("Referer");
        }
        // 允许哪些Origin发起跨域请求
        response.setHeader( "Access-Control-Allow-Origin", origin );
        // 允许请求的方法
        response.setHeader( "Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT" );
        //多少秒内,不需要再发送预检验请求,可以缓存该结果
        response.setHeader( "Access-Control-Max-Age", "3600" );
        // 表明它允许跨域请求包含xxx头
        response.setHeader( "Access-Control-Allow-Headers", "*" );
        //是否允许浏览器携带用户身份信息(cookie)
        response.setHeader( "Access-Control-Allow-Credentials", "true" );
        //prefight请求
        if (request.getMethod().equals( "OPTIONS" )) {
            response.setStatus( 200 );
            return;
        }
        chain.doFilter( servletRequest, response );
    }

    @Override
    public void destroy() {

    }
}

其实看到这里,大家会发现依然是篇头讲到的自定义过滤器解决跨域问题的方法,只不过对其中的响应头做了必要的调整。

客户端也不再需要在请求头中带上token了,只要登录之后不管调什么接口都会自动带上cookie到后端校验的,代码如下:

    $.ajax({
        url:'http://localhost:8080/win/api/test/cors',
        type:'post',
         beforeSend:(xhr)=> {
           //xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
           //xhr.setRequestHeader("token", "web_session_key-5ce2ae9c-8f79-4f83-9b47-1510da4b2fb0");
           xhr.setRequestHeader("device","H5");
        },
        xhrFields:{
            withCredentials:true,
            useDefaultXhrHeader:false
        },
        corssDomain:true,
        success:function(data){
        console.log(data);
        }
    });

这样接口就可以正常返回数据了,控制台也不再报错(注意request header中的cookie)。
在这里插入图片描述

修改shiro默认的会话管理器(方法三)

查看源码(具体执行流程可参考文末提供的参考文档获悉)可以知道shiro是在其默认的会话管理器DefaultWebSessionManager中获取请求携带过来的cookie的,我们可以通过继承这个类来扩展其中相关的代码来实现我们的需求,下面先贴出的是shiro从cookie中获取sessionid的主要源代码:

    @Override
    public Serializable getSessionId(SessionKey key) {
        Serializable id = super.getSessionId(key);
        if (id == null && WebUtils.isWeb(key)) {
            ServletRequest request = WebUtils.getRequest(key);
            ServletResponse response = WebUtils.getResponse(key);
            id = getSessionId(request, response);
        }
        return id;
    }

    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        return getReferencedSessionId(request, response);
    }
    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
        String id = getSessionIdCookieValue(request, response);
        .......
        return id;
    }
    private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
        .......
        //getSessionIdCookie().readValue()操作的是cookie对象.
        return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
    }

那么我们只要在继承类重写这些方法,通过在请求头传输过来的device标识便可以区分出不同的调用端来源,即pc端后台依然采用shiro原有的认证方式,而app端或者h5应用则可以使用基于token的身份认证方式,达到两者共存的目的.下面来看看我们自定义CustomerWebSessionManager类,其继承了shiro的DefaultWebSessionManager类,由于DefaultWebSessionManager中的大部分方法为私有的方法,无法为其子类所继承,所以只好重写其中所有用到的protected方法,代码如下:

 private static final Logger logger = LoggerFactory.getLogger(CustomerWebSessionManager.class);

    private static final String AUTH_TOKEN = "token";

    public CustomerWebSessionManager() {
        super();
    }

    /**
     * 重写父类获取sessionID的方法,若请求为H5则从请求头中取出token,若为PC端后台则从cookie中获取
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        if (!(request instanceof HttpServletRequest)) {
            logger.debug("Current request is not an HttpServletRequest - cannot get session ID.  Returning null.");
            return null;
        }
        HttpServletRequest httpRequest = WebUtils.toHttp(request);
        if (StringUtils.isNotBlank(httpRequest.getHeader("device"))
                && httpRequest.getHeader("device").equals("H5")) {
            //从header中获取token
            String token = httpRequest.getHeader(AUTH_TOKEN);
            // 每次读取之后都把当前的token放入response中
            HttpServletResponse httpResponse = WebUtils.toHttp(response);
            if (StringUtils.isNotEmpty(token)) {
                httpResponse.setHeader(AUTH_TOKEN, token);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            }
            //sessionIdUrlRewritingEnabled的配置为false,不会在url的后面带上sessionID
            request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
            return token;
        }
        return getReferencedSessionId(request, response);
    }

    /**
     * shiro默认从cookie中获取sessionId
     *
     * @param request
     * @param response
     * @return
     */
    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
        String id = getSessionIdCookieValue(request, response);
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                    ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
        } else {
            //not in a cookie, or cookie is disabled - try the request URI as a fallback (i.e. due to URL rewriting):
            //try the URI path segment parameters first:
            id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
            if (id == null) {
                //not a URI path segment parameter, try the query parameters:
                String name = getSessionIdName();
                id = request.getParameter(name);
                if (id == null) {
                    //try lowercase:
                    id = request.getParameter(name.toLowerCase());
                }
            }
            if (id != null) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
                        ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
            }
        }
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            //automatically mark it valid here.  If it is invalid, the
            //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        }
        // always set rewrite flag - SHIRO-361
        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
        return id;
    }

    //copy from DefaultWebSessionManager
    private String getSessionIdCookieValue(ServletRequest request, ServletResponse response) {
        if (!isSessionIdCookieEnabled()) {
            logger.debug("Session ID cookie is disabled - session id will not be acquired from a request cookie.");
            return null;
        }
        if (!(request instanceof HttpServletRequest)) {
            logger.debug("Current request is not an HttpServletRequest - cannot get session ID cookie.  Returning null.");
            return null;
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
    }

    //since 1.2.2 copy from DefaultWebSessionManager
    private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
        if (!(servletRequest instanceof HttpServletRequest)) {
            return null;
        }
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String uri = request.getRequestURI();
        if (uri == null) {
            return null;
        }
        int queryStartIndex = uri.indexOf('?');
        if (queryStartIndex >= 0) { //get rid of the query string
            uri = uri.substring(0, queryStartIndex);
        }
        int index = uri.indexOf(';'); //now check for path segment parameters:
        if (index < 0) {
            //no path segment params - return:
            return null;
        }
        //there are path segment params, let's get the last one that may exist:
        final String TOKEN = paramName + "=";
        uri = uri.substring(index + 1); //uri now contains only the path segment params
        //we only care about the last JSESSIONID param:
        index = uri.lastIndexOf(TOKEN);
        if (index < 0) {
            //no segment param:
            return null;
        }
        uri = uri.substring(index + TOKEN.length());
        index = uri.indexOf(';'); //strip off any remaining segment params:
        if (index >= 0) {
            uri = uri.substring(0, index);
        }
        return uri; //what remains is the value
    }

    //since 1.2.1 copy from DefaultWebSessionManager
    private String getSessionIdName() {
        String name = this.getSessionIdCookie() != null ? this.getSessionIdCookie().getName() : null;
        if (name == null) {
            name = ShiroHttpSession.DEFAULT_SESSION_ID_NAME;
        }
        return name;
    }

当shiro取不到sessionid时,会调用DelegatingSubject类中的getSession(true)方法创建一个新的session。

    public Session getSession(boolean create) {
        if (log.isTraceEnabled()) {
            log.trace("attempting to get session; create = " + create +
                    "; session is null = " + (this.session == null) +
                    "; session has id = " + (this.session != null && session.getId() != null));
        }

        if (this.session == null && create) {

            //added in 1.2:
            if (!isSessionCreationEnabled()) {
                String msg = "Session creation has been disabled for the current subject.  This exception indicates " +
                        "that there is either a programming error (using a session when it should never be " +
                        "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " +
                        "for the current Subject.  See the " + DisabledSessionException.class.getName() + " JavaDoc " +
                        "for more.";
                throw new DisabledSessionException(msg);
            }

            log.trace("Starting session for host {}", getHost());
            SessionContext sessionContext = createSessionContext();
            Session session = this.securityManager.start(sessionContext);
            this.session = decorate(session);
        }
        return this.session;
    }

上面的第22行this.securityManager.start最终调用的是DefaultWebSessionManager中的onStart方法,所以我们要重写这个方法,将产生的sessionid作为token放到response header中。另外当session失效或销毁时的相关方法也需重新实现,具体代码如下:

 //存储会话id到response header中
    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);
        }
        String idString = currentId.toString();
        if (StringUtils.isNotBlank(request.getHeader("device"))
                && request.getHeader("device").equals("H5")) {
            response.setHeader(AUTH_TOKEN, idString);
        } else {
            Cookie template = getSessionIdCookie();
            Cookie cookie = new SimpleCookie(template);
            cookie.setValue(idString);
            cookie.saveTo(request, response);
        }
        logger.trace("Set session ID cookie for session with id {}", idString);
    }

    //设置deleteMe到response header中
    private void removeSessionIdCookie(HttpServletRequest request, HttpServletResponse response) {
        if (StringUtils.isNotBlank(request.getHeader("device"))
                && request.getHeader("device").equals("H5")) {
            response.setHeader(AUTH_TOKEN, Cookie.DELETED_COOKIE_VALUE);
        } else {
            getSessionIdCookie().removeFrom(request, response);
        }
    }

    /**
     * 会话创建
     * Stores the Session's ID, usually as a Cookie, to associate with future requests.
     *
     * @param session the session that was just {@link #createSession created}.
     */
    @Override
    protected void onStart(Session session, SessionContext context) {
        super.onStart(session, context);
        if (!WebUtils.isHttp(context)) {
            logger.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 {
            logger.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);
    }
    //会话失效
    @Override
    protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
        super.onExpiration(s, ese, key);
        onInvalidation(key);
    }

    @Override
    protected void onInvalidation(Session session, InvalidSessionException ise, SessionKey key) {
        super.onInvalidation(session, ise, key);
        onInvalidation(key);
    }

    private void onInvalidation(SessionKey key) {
        ServletRequest request = WebUtils.getRequest(key);
        if (request != null) {
            request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID);
        }
        if (WebUtils.isHttp(key)) {
            logger.debug("Referenced session was invalid.  Removing session ID cookie.");
            removeSessionIdCookie(WebUtils.getHttpRequest(key), WebUtils.getHttpResponse(key));
        } else {
            logger.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
                    "pair. Session ID cookie will not be removed due to invalidated session.");
        }
    }
    //会话销毁
    @Override
    protected void onStop(Session session, SessionKey key) {
        super.onStop(session, key);
        if (WebUtils.isHttp(key)) {
            HttpServletRequest request = WebUtils.getHttpRequest(key);
            HttpServletResponse response = WebUtils.getHttpResponse(key);
            logger.debug("Session has been stopped (subject logout or explicit stop).  Removing session ID cookie.");
            removeSessionIdCookie(request, response);
        } else {
            logger.debug("SessionKey argument is not HTTP compatible or does not have an HTTP request/response " +
                    "pair. Session ID cookie will not be removed due to stopped session.");
        }
    }

到这里,对DefaultWebSessionManager中方法的重写就已经完成了,在onStart()调用我们重写的storeSessionId()方法可以看出是将新创建的sessionid作为token的值设置到响应头中,在后续请求时带上,在重写的getSessionId()中获取,交给后续方法进行校验。

最后再在springboot中做如下配置:

  @Bean
    public SimpleCookie wapsession() {
        SimpleCookie simpleCookie = new SimpleCookie("token");
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }

    /**
     * 自定义CustomerWebSessionManager实现shiro session的管理
     */
    @Bean
    public CustomerWebSessionManager sessionManager() {
        CustomerWebSessionManager sessionManager = new CustomerWebSessionManager();
        //会话验证器调度时间
        sessionManager.setSessionValidationInterval(1800000);
        //定时检查失效的session
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true
        sessionManager.setDeleteInvalidSessions(true);
        sessionManager.setSessionDAO(sessionDAO());
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionIdCookie(wapsession());
        sessionManager.setSessionIdCookieEnabled(true);
        Collection<SessionListener> listeners = new ArrayList<SessionListener>();
        listeners.add(new BDSessionListener());
        sessionManager.setSessionListeners(listeners);
        return sessionManager;
    }

将token设到cookie中并告诉sessionManager作为会话id,前端测试代码示例如下:

          $.ajax({
              type: "POST",
              url:  "http://localhost:2018/login",
              data: $('#signupForm').serialize(),
              success: function (res,textStatus, request){
                  console.log(request.getAllResponseHeaders());
                  if (res.code == 0) {
                      $.ajax({
                          type: "POST",
                          url:  "http://localhost:2018/agency/customer/postSuccess",
                          beforeSend: function (XMLHttpRequest) {
                              XMLHttpRequest.setRequestHeader("token",request.getResponseHeader('token'));
                          },
                          success: function (r) {
                              document.write(r);
                          }
                      });
                  } else {
                      layer.msg(res.msg);
                  }
              },

          });
      }

此外,还应设置全局的ajax请求头:

<script type="text/javascript">
    $.ajaxSetup({
        headers: {
            "device": "H5"
        }
    })
</script>

在第一次登陆之后,可通过request.getResponseHeader(‘token’)将获取到的token存储起来或将token设置为全局的请求头,方便后续访问时带上,下面是由上面编写的代码的测试结果(注意其中request header和response header中的token):
在这里插入图片描述
在这里插入图片描述

到此,对shiro框架进行跨域请求的全部方法全部介绍完了。

参考文档:
[1]: https://www.jianshu.com/p/511d3adf95e9
[2]: https://www.jianshu.com/p/1c40cba55e0a
[3]: https://blog.csdn.net/wkyseo/article/details/79023191

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值