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"
    />

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM