精华内容
下载资源
问答
  • shiro跨域请求认证的解决方案 大家在写项目时可能会遇到前后端分离的情况,那么这个时候就会遇到跨域请求的问题。我们知道在web环境下http是一种无状态的通讯协议,要想记录和校验用户的登录状态必须通过session的...

    大家在写项目时可能会遇到前后端分离的情况,那么这个时候就会遇到跨域请求的问题。我们知道在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

    展开全文
  • 最近在做一个项目,用shiro做权限。一个人测试的时候没问题,涉及前后端分开就出问题了。shiro跨域用户信息存不到cookie里。 跨域设置都写了的。
  • Shiro跨域问题

    2021-04-01 10:15:34
    所以在springMvc里面的跨域配置无效, 需要自定义一个过滤器优先级比shiro的过滤器更高 下面是跨域配置: 1.springMvc过滤器的跨域配置 @Configuration public class CrossOriginConfig { @Bean public CorsFilter...

    shiro过滤器 springMvc过滤器(CorsFilter)的执行先后顺序在这里插入图片描述

    所以在springMvc里面的跨域配置无效, 需要自定义一个过滤器优先级比shiro的过滤器更高

    在这里插入图片描述

    下面是跨域配置:

    1.springMvc过滤器的跨域配置

    @Configuration
    public class CrossOriginConfig {
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    
            CorsConfiguration config = new CorsConfiguration();
            config.addAllowedOrigin("*");
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            config.setMaxAge(3600L);
            config.setAllowCredentials(true);
    
            source.registerCorsConfiguration("/**", config);
            return new CorsFilter(source);
    
        }
    }
    

    2.自定义过滤器的配置

    第一步:定义过滤器

    public class MyCorsFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
    
            response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
            response.addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            if ("OPTIONS".equals(request.getMethod())) {
                response.setStatus(HttpStatus.NO_CONTENT.value());
                return;
            } else {
                filterChain.doFilter(request, response);
            }
    
        }
    }
    

    第二步: 注册过滤器, 并设置优先级最高

    @Configuration
    public class  FilterConfig{
    
        @Bean
        public FilterRegistrationBean replaceTokenFilter(){
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST);
            registration.setFilter( new MyCorsFilter());
            registration.addUrlPatterns("/*");
            registration.setName("myCorsFilter ");
            //设置优先级最高
            registration.setOrder(1);
            return registration;
        }
    }
    
    展开全文
  • 基于fetch cors + shiro 跨域解决方案

    千次阅读 2017-05-24 19:08:37
    基于fetch cors + shiro 跨域解决方案 export function request(url, options) { let pack={url:url,options:options}; base(pack); return fetch(pack.url,pack.options) .then(checkStatus) .then(parseJSO

    基于fetch cors + shiro 跨域解决方案

    
    export  function request(url, options) { 
      let pack={url:url,options:options};
      base(pack);
      return fetch(pack.url,pack.options)
        .then(checkStatus)
        .then(parseJSON)
        .then(data => ({ data }))
        .catch(err => ({ err }));
    }
    
    function base(pack){
     pack.url=host+pack.url;
     cookie(pack.options); 
    }
    function cookie(options){  
       Object.assign(options,{credentials: 'include',mode: 'cors'}); 
    }
    

    后台ssm + shiro

        @WebFilter("/*")
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "http://localhost:8000");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE");
            httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", "token,Access-Control-Allow-Origin,Access-Control-Allow-   Methods,Access-Control-Max-Age,authorization");
            httpServletResponse.setHeader("(Content-Type","application/json; charset=utf-8");
            chain.doFilter(request, httpServletResponse);
        }

    遇到的问题

    当headers: { ‘Content-Type’: ‘application/json’,} 后台取不到数据

    • 解决方法
    • 将headers 设置为headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’,}
    • 将json 转为FormDATA
    function JSONtoForData(data){
        //let fData=new FormData();
        let fData=[];
         for(let key in data){
           //fData.append(key,data[key]);
           fData.push(`${key}=${data[key]}`);
        }
    
        return {body:fData.join("&")};
    }

    ####问题

    Fetch API cannot load http://localhost/IBaseUser/dologin. The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:8000' is therefore not allowed access
    1. Access-Control-Allow-Origin 不允许为 “*”
      2.解决方法 在后台中 将 Access-Control-Allow-Origin 设置 为 前端页面的 地址 我这里是
     httpServletResponse.setHeader("Access-Control-Allow-Origin", "http://localhost:8000");
    展开全文
  • shiro跨域问题

    2019-04-09 11:37:37
    在后台使用shiro权限框架的时候,如果前后端分离,则每个请求都是跨域请求,shiro跨域请求每次都是生成一条session。这样会导致很多bug,比如在登录的时候,获取验证码和登录用的不是同一个session,所以验证就...

    在后台使用shiro权限框架的时候,如果前后端分离,则每个请求都是跨域请求,shiro对跨域请求每次都是生成一条session。这样会导致很多bug,比如在登录的时候,获取验证码和登录用的不是同一个session,所以验证就无法比较,在登录的session里面获取验证码会一直获取到一个空值。解决方案是如果后台要进行跨域配置,然后前端的请求配置里面一定一定要加上

    withCredentials: true

    展开全文
  • Spring boot + shiro 跨域配置(解决jsessionid丢失问题)
  • debug工作代码,解析shiro跨域部署,为啥session信息获取不到
  • Spring boot + shiro 跨域失效

    千次阅读 2019-09-19 11:50:06
    在 Springboot 中解决跨域有好几种方式,比如: 使用 @CrossOrigin 注解 实现 WebMvcConfigurer,然后...原因是:shiro的过滤器会在跨域处理之前执行,这就导致未允许跨域的请求先到达shiro过滤器,这样就会出现...
  • shiro跨域的问题 出现的问题: 使用了shiro框架,开启了shiro的登陆验证过滤器时,即filterChainDefinitionMap.put("/**","user");,代表要登陆过才能进行访问,但是经过一番测试,发现当ajax请求为复杂请求时,...
  • 解决Shiro跨域问题

    万次阅读 2019-06-22 16:55:47
    由于项目需要springboot项目接入了shiro进行登录验证。做法是在在每一个接口的heards部分增加token,后台拿到token后进行验证。在postman测试没问题。但是,前端却一直报options500错误。 原来,是浏览器的同源...
  • shiro跨域CORS问题处理

    千次阅读 2020-03-02 00:03:02
    前言 跨域问题的基本处理查看上一篇文章:...如果是使用的shiro以上步骤都试过了,还有如下问题,请看本文章的解决办法。 问题 把主要提示拷贝出来 A cookie associated with a cross-site resource at htt...
  • 想深入学习 【java】【大数据】【微服务】【前端】的同学,可以关注我的公众号 【不正经的码农】 回复对应的关键字即可免费领取。 ## 拦截器判断 拦截器截取到请求先进行判断,如果...import org.apache.shiro.Securi.
  • 问题描述 前后台分离的项目会存在跨域的问题,正常解决跨域问题分...重点要说的是Shiro跨域问题 正常套路来说,shiro安全校验返回结果是这样的 红框中location字段为浏览器重定向地址 这样的话,前端就又回...
  • 1.springboot跨域解决 添加配置以下配置即可解决 @Configuration public class CorsConfig implements WebMvcConfigurer { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() {...
  • SpringBoot2.0 + Shiro 跨域问题 踩坑记录

    千次阅读 2019-05-19 01:54:19
    最近一个项目用了gitee上“ruoyi”的Web框架(SpringBoot2.0 + Shiro + Mybatis)。给大家推荐一下,开源的洗剥干净的成品,可以直接拿过来进行二次开发,对于从未接触过Springboot而又想进行框架升级转型的项目来说...
  • 前言:前后端分离,业务分离,网关路由等已经成为当下web application开发的流行趋势。前端以单页面路由为核心的框架为主体,...在这样的开发模式下,首先需要解决的就是由于跨域而引起的访问,cookie传递以及权限...
  • springboot+shiro 跨域解决(OPTIONS)

    千次阅读 2019-12-20 11:51:00
    拦截器判断 拦截器截取到请求先进行判断,如果是OPTIONS请求的话,则放行 import com.alibaba.fastjson.JSON; ... import org.apache.commons.lang.StringUtils; import org.apache.shiro.SecurityUtil...
  • // 预检间隔时间 } } 如果你的spring boot后台整合了shiro,那上面的配置对走shiro的请求不会生效,浏览器还是会提示跨域,因此我们用下列方法设置允许跨域访问 import org.springframework.boot....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,903
精华内容 1,161
关键字:

shiro跨域