問題:
集成shiro之后發現配置放行的接口可以正常訪問,而需要登錄驗證的接口會報錯
其中OPTIONS類型的接口會報302
導致后續的post請求報錯提示跨域問題
Shiro登錄流程
首先Shiro是根據請求中cookie攜帶的JSESSIONID判斷是否登錄的
當調用登錄接口登錄成功時,后端的響應頭會添加一個set-cookie的參數
JSESSIONID代表當前登錄的用戶,前端只要在請求中攜帶這個參數Shiro就可以識別出用戶並放行。
但是post請求會先發送一個OPTIONS類型的探測請求,由於這個請求沒有攜帶JSESSIONID,Shiro就會判斷為未登錄,進行攔截。
所以解決的思路就是放行OPTIONS類型的請求。
解決
- 創建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());
}
}
- 在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;
}
}