springboot shiro 前后端分离,解决跨域过虑options请求shiro管理session问题模拟跨域请求

Posted L12345

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot shiro 前后端分离,解决跨域过虑options请求shiro管理session问题模拟跨域请求相关的知识,希望对你有一定的参考价值。

一、解决跨域、过虑options请求问题

1.创建过虑类


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@WebFilter(filterName = "SimpleCORSFilter",urlPatterns ="/*" )
public class SimpleCORSFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(SimpleCORSFilter.class);
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        logger.info("请求拦截:"+ ((HttpServletRequest) request).getRequestURI());
        /*
         *  设置允许跨域请求
         */
        httpServletResponse.setHeader("Access-Control-Allow-Headers", "content-type, accept");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST");
        httpServletResponse.setStatus(200);
        httpServletResponse.setContentType("text/plain;charset=utf-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        httpServletResponse.setHeader("Access-Control-Max-Age", "0");
        httpServletResponse.setHeader("Access-Control-Allow-Headers",
                "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token,WG-Token, Authorization");
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.setHeader("XDomainRequestAllowed", "1");

         /*
            过虑 OPTIONS 请求
         */
        String type = httpServletRequest.getMethod();
        if (type.toUpperCase().equals("OPTIONS")) {
            return;
        }

        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
//        String isCrossStr = filterConfig.getInitParameter("IsCross");
//        isCross = isCrossStr.equals("true") ? true : false;
//        System.out.println(isCrossStr);
    }
    @Override
    public void destroy() {
//        isCross = false;
    }

}

 

2.在 ShiroConfig.java 中设置拦截器

Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>();
        filterMap.put("/sys/*",new SimpleCORSFilter()); // SimpleCORSFilter 为步骤1中创建的类 
        shiroFilterFactoryBean.setFilters(filterMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/index.html*", "anon");
        //anon下不需要认证
        filterChainDefinitionMap.put("/anon/**", "anon");
        filterChainDefinitionMap.put("/loginSys", "anon");
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/back", "anon");
        filterChainDefinitionMap.put("/sys/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

 

二、前后端分离shiro管理session问题

SessionManager.java代码参考https://blog.csdn.net/palerock/article/details/73457415?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

1.重写DefaultWebSessionManager的getSessionId方法

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;


public class SessionManager extends DefaultWebSessionManager {
    private static final Logger log = LoggerFactory.getLogger(DefaultWebSessionManager.class);
    private String authorization = "Authorization";

    /**
     * 重写获取sessionId的方法调用当前Manager的获取方法
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        return this.getReferencedSessionId(request, response);
    }

    /**
     * 获取sessionId从请求中
     *
     * @param request
     * @param response
     * @return
     */
    private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
        String id = this.getSessionIdCookieValue(request, response);
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
        } else {
            id = this.getUriPathSegmentParamValue(request, "JSESSIONID");
            if (id == null) {
                // 获取请求头中的session
                id = WebUtils.toHttp(request).getHeader(this.authorization);
                if (id == null) {
                    String name = this.getSessionIdName();
                    id = request.getParameter(name);
                    if (id == null) {
                        id = request.getParameter(name.toLowerCase());
                    }
                }
            }
            if (id != null) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url");
            }
        }

        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
        }

        return id;
    }

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

    // copy from super class
    private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {
        if (!(servletRequest instanceof HttpServletRequest)) {
            return null;
        } else {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String uri = request.getRequestURI();
            if (uri == null) {
                return null;
            } else {
                int queryStartIndex = uri.indexOf(63);
                if (queryStartIndex >= 0) {
                    uri = uri.substring(0, queryStartIndex);
                }

                int index = uri.indexOf(59);
                if (index < 0) {
                    return null;
                } else {
                    String TOKEN = paramName + "=";
                    uri = uri.substring(index + 1);
                    index = uri.lastIndexOf(TOKEN);
                    if (index < 0) {
                        return null;
                    } else {
                        uri = uri.substring(index + TOKEN.length());
                        index = uri.indexOf(59);
                        if (index >= 0) {
                            uri = uri.substring(0, index);
                        }

                        return uri;
                    }
                }
            }
        }
    }

    // copy from super class
    private String getSessionIdName() {
        String name = this.getSessionIdCookie() != null ? this.getSessionIdCookie().getName() : null;
        if (name == null) {
            name = "JSESSIONID";
        }

        return name;
    }
}

 

2.在 ShiroConfig.java 配置SessionManager

   
@Bean
public SecurityManager securityManager(MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setSessionManager(sessionManager());
return securityManager;
}

  @Bean("sessionManager")
public SessionManager sessionManager(){
SessionManager manager = new SessionManager(); // SessionManager 为步骤1中创建的 SessionManager
/*使用了shiro自带缓存,
如果设置 redis为缓存需要重写CacheManager(其中需要重写Cache)
manager.setCacheManager(this.RedisCacheManager());*/
manager.setSessionDAO(new EnterpriseCacheSessionDAO());
return manager;
}

 

 3.用户登录成功后,将sessionId返回前端


@ResponseBody
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
public String login(
@RequestParam(required = false) String username,
@RequestParam(required = false) String password
) {

JSONObject jsonObject = new JSONObject();
Subject subject = SecurityUtils.getSubject();
password = MD5Tools.MD5(password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);

try {
// 登录,即身份验证
    subject.login(token);
    onlineSessionManager.addOnlineSession(subject.getSession().getId());
    User user = userService.getUserByLoginName(token.getUsername());
        // 在session中存放用户信息
    subject.getSession().setAttribute("userLogin", user);
    jsonObject.put("error", 0);
    jsonObject.put("msg", "登录成功");
    // 返回sessionId
    jsonObject.put("sessionId",subject.getSession().getId());
} catch (IncorrectCredentialsException e) {
throw new JsonException("用户名或密码错误", 405);
} catch (LockedAccountException e) {
throw new JsonException("登录失败,该用户已被冻结", 405);
} catch (AuthenticationException e) {
throw new JsonException("用户名或密码错误", 405);
}
return jsonObject.toString();
}

 

4.前端请求时请求头带上sessionId 

模拟跨域请求的两种方法:

方法一:启动本地项目,浏览器随便打开某个网站,F12 > Console下执行以下代码实现跨域请求,最方便也是最容易实现

可参考:https://www.cnblogs.com/L237/p/12556488.html

方法二:启动本地项目,在自己的某台服务器上的某个项目下创建一个test.jsp页面,放入以下代码,然后通过浏览器访问test.jsp实现跨域请求

        $.ajax({
            type:\'post\',
            url:\'http://192.168.1.31:8080/login\',
            data:\'{"username":"admin","password":"admin"}\',
            contentType: "application/json", //提交数据类型
            dataType:\'json\',
            success:function(data){
                console.log(data);
                var sessionId = data.data.sessionId; //登录成功后返回的sessionId
                $.ajax({
                    type:\'post\',
                    url:\'http://192.168.1.31:8080/sys/noticeNews/selectList\',
                    data:\'{"pageNum":"1","pageSize":"10","verify":"2"}\',
                    contentType: "application/json", //提交数据类型
                    headers: {
                        "Authorization": sessionId,  // Authorization 为重定的SessionManager类中定义的属性
                        "Content-Type":"application/json;charset=utf8"
                    },
                    dataType:\'json\',
                    crossDomain: true,
                    success:function(data){
                        console.log(data);
                    },
                    error:function (msg){
                        console.log(msg);
                    }
                });
            },
            error:function (msg){
                console.log(msg);
                layer.msg("用户名或密码错误");
            }
        });

 

 5. 在解决跨域类(一、1)SimpleCORSFilter中的httpServletResponse.setHeader() 中添加(二、4)Authorization

 

shiro配置类参考  ShiroConfig.java

package com.sx.ucenter.shiro;

import com.sx.common.util.SimpleCORSFilter;
import com.sx.ucenter.filter.SysFilter;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //将自己的验证方式加入容器
    @Bean
    public MyRealm myShiroRealm(EhCacheManager ehCacheManager) {
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }

    //权限管理,配置主要是Realm的管理认证
    @Bean
    public SecurityManager securityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setSessionManager(sessionManager());
      return securityManager;
    }

//Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new LinkedHashMap<>();

        /*
         *常用的过滤器如下:
         *anon:所有用户可访问,通常作为指定页面的静态资源时使用
         *authc:所有已登陆用户可访问
         *roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致
         *perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致
         */
//        //配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
//        shiroFilterFactoryBean.setLoginUrl("/unauth");


        /*跨域请求设置 start  */
        Map<String, Filter> filterMap = new LinkedHashMap<String, Filter>();
//        filterMap.put("authc",new SimpleCORSFilter());
        filterMap.put("/sys/*",new SimpleCORSFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/index.html*", "anon");
        //anon下不需要认证
        filterChainDefinitionMap.put("/anon/**", "anon");
        filterChainDefinitionMap.put("/loginSys", "anon");
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/back", "anon");
        filterChainDefinitionMap.put("/sys/**", "authc");
//        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        /*跨域请求设置 end  */

        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //开放匿名访问
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");

        return shiroFilterFactoryBean;
    }

    /*
     * 设置session
     */
    @Bean("sessionManager")
    public SessionManager sessionManager(){
        SessionManager manager = new SessionManager();
        /*使用了shiro自带缓存,
        如果设置 redis为缓存需要重写CacheManager(其中需要重写Cache)
        manager.setCacheManager(this.RedisCacheManager());*/
        manager.setSessionDAO(new EnterpriseCacheSessionDAO());
        return manager;
    }

    //加入注解的使用,不加入这个注解不生效
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

 

6.遇到的问题

参考:https://blog.csdn.net/wangjun5159/article/details/89875746

按照以上方式可以实现shiro 的管理了,但是发现登录成功后只要停2分钟没有任何操作 session就会失效需要重新登录,

后面发现是因为设置了ehcache缓存的原因,当时的ehcache设置如下:

timeToIdleSeconds配置为2分钟,如果2分钟内没有交互,session就会从ehcache中删除

    <!-- 是否永久保存 -->
    <!-- 最大空闲时间  单位:秒 -->
    <!-- 存活时间 -->
    <!-- 溢出到磁盘 -->
    <!-- 磁盘上最大存储的对象数 -->
    <!-- 服务器重启后磁盘上的数据是否有效 -->
    <!-- 每隔多长时间去开启一次线程清理数据 -->
    <!-- 淘汰策略    最近一段时间利用率低的会被优先清理掉 -->
    <defaultCache
            maxElementsInMemory="10000" 
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />

 

以上是关于springboot shiro 前后端分离,解决跨域过虑options请求shiro管理session问题模拟跨域请求的主要内容,如果未能解决你的问题,请参考以下文章

Vue +Springboot+shiro实现前后端分离权限控制

Springboot + Vue + shiro 实现前后端分离权限控制

前后端分离 springboot整合shiro

SpringBoot+Shiro框架整合实现前后端分离的权限管理基础Demo

一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器

Springboot学习SpringBoot集成Shiro前后端分离使用redis做缓存个人博客搭建