1. 程式人生 > 其它 >SpringBoot+Shiro放行OPTIONS請求,解決跨域問題

SpringBoot+Shiro放行OPTIONS請求,解決跨域問題

問題:

整合shiro之後發現配置放行的介面可以正常訪問,而需要登入驗證的介面會報錯

其中OPTIONS型別的介面會報302

導致後續的post請求報錯提示跨域問題


Shiro登入流程

首先Shiro是根據請求中cookie攜帶的JSESSIONID判斷是否登入的
當呼叫登入介面登入成功時,後端的響應頭會新增一個set-cookie的引數

JSESSIONID代表當前登入的使用者,前端只要在請求中攜帶這個引數Shiro就可以識別出使用者並放行。
但是post請求會先發送一個OPTIONS型別的探測請求,由於這個請求沒有攜帶JSESSIONID,Shiro就會判斷為未登入,進行攔截。
所以解決的思路就是放行OPTIONS型別的請求。

解決

  1. 建立ShiroUserFilter
package com.school.service.config;

import com.school.service.entity.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
public class ShiroUserFilter extends UserFilter {
    /**
     * 在訪問過來的時候檢測是否為OPTIONS請求,如果是就直接返回true
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            log.info("OPTIONS放行");
            setHeader(httpRequest,httpResponse);
            return true;
        }
        return super.preHandle(request,response);
    }

    /**
     * 該方法會在驗證失敗後呼叫,這裡由於是前後端分離,後臺不控制頁面跳轉
     * 因此重寫改成傳輸JSON資料
     */
    @Override
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        setHeader((HttpServletRequest) request,(HttpServletResponse) response);
        PrintWriter out = response.getWriter();
        //自己控制返回的json資料
        out.println(new Result("500",null,"Shiro驗證失敗"));
        out.flush();
        out.close();
    }

    /**
     * 為response設定header,實現跨域
     */
    private void setHeader(HttpServletRequest request, HttpServletResponse response){
        //跨域的header設定
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", request.getMethod());
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        //防止亂碼,適用於傳輸JSON資料
        response.setHeader("Content-Type","application/json;charset=UTF-8");
        response.setStatus(HttpStatus.OK.value());
    }
}

  1. 在Shiro配置類的shiroFilter中新增進去
package com.school.service.config;

import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * Shiro配置類
 */
@Configuration
public class ShiroConfig {

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/school/goToLogin");//設定登入頁面
        shiroFilterFactoryBean.setUnauthorizedUrl("/school/goToLogin");//許可權不足跳轉頁面,這個在Default過濾器中設定無效,具體看 https://blog.csdn.net/bicheng4769/article/details/86680955

        //新增自定義Filter,放行OPTIONS請求-----------------------------------------這裡✨✨✨✨✨✨✨✨
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authc", new ShiroUserFilter());
        shiroFilterFactoryBean.setFilters(filters);
        //----------------------------------------------------------------------------------------------✨

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterChainDefinitionMap.put("/service/school/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
        filterChainDefinitionMap.put("/configuration/security", "anon");
        filterChainDefinitionMap.put("/configuration/ui", "anon");

        filterChainDefinitionMap.put("/service/article/**", "authc");
        filterChainDefinitionMap.put("/service/chat/**", "authc");
        filterChainDefinitionMap.put("/service/diary/**", "authc");
        filterChainDefinitionMap.put("/service/file/**", "authc");
        filterChainDefinitionMap.put("/service/problem/**", "authc");
        filterChainDefinitionMap.put("/service/team-article/**", "authc");
        filterChainDefinitionMap.put("/service/team/**", "authc");
        filterChainDefinitionMap.put("/service/user/**", "authc");
        filterChainDefinitionMap.put("/service/user-friend/**", "authc");
        filterChainDefinitionMap.put("/service/user-info/**", "authc");
        filterChainDefinitionMap.put("/service/widget/**", "authc");

        //主要這行程式碼必須放在所有許可權設定的最後,不然會導致所有 url 都被攔截 剩餘的都需要認證
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

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

    @Bean
    public MyRealm myRealm (){
        MyRealm myRealm = new MyRealm();
        return myRealm;
    }


}

一切正常