Spring boot 和 Shiro 作後臺跨域訪問權限控制遇到的問題

  最近在弄一個後臺用Spring boot、Shiro、Spring data mybatis,前臺用Angular 4的項目.在作權限控制的時候後臺一直獲取不到前臺獲取的數據(username, password, token等)。記錄一下解決過程。html

  首先爲了解決密碼登陸和Token驗證兩種驗證方法,因此自定義了一個JWTOrAuthenticationFilter來根據前臺傳來的數據判斷選擇哪種登陸方法(具體參考)。java

  固然想象是美好的,定義完成後發現每次訪問時傳入JWTOrAuthenticationFilter的onAccessDenied時傳入的Request數據都是空的,前臺調試發現請求時數據是發送了的。git

  • JWTOrAuthenticationFilter訪問異常github

  首先配置了/login=anon指望訪問正常登錄的時候不經過Shiro的過濾器,而是訪問Controller中定義的login方法。可是調試發現登陸時根本沒有進入Controller中定義的login方法,而是直接進入了JWTOrAuthenticationFilter。web

  Google一下找到一篇帖子( http://www.hillfly.com/2017/179.html)。原來將自定義Shiro Filter註冊爲Spring Bean時,會被自動註冊到全局的ApplicationFilterChain中,這個自定義的Filter不管如何都會執行,因此/login=anon配置失效了。spring

  修改辦法:不顯式的將 JWTOrAuthenticationFilter 註冊爲 Spring bean 。代碼以下:跨域

protected ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager     
                                        securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
     ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
     filterFactoryBean.setLoginUrl(loginUrl);
     filterFactoryBean.setSuccessUrl(successUrl);
     filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);
     filterFactoryBean.setSecurityManager(securityManager);
     filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
     Map<String, Filter> filters = new HashMap<>();
     filters.put("authc", new JWTOrAuthenticationFilter(origin));
     filterFactoryBean.setFilters(filters);
     return filterFactoryBean;
 }
  • JS的CROS請求的異常服務器

  通過上面的修改,正常登陸終於能夠成功了,撒花。。。。 可是問題又來了。。登陸是能夠成功了, 其餘請求發送Token驗證時應該通過 JWTOrAuthenticationFilter 去調用Token的登陸驗證Realm了, 可是 JWTOrAuthenticationFilter 獲取不到前臺發送的Token, 也就是隻能登陸,其餘什麼都幹不了......MDZZ。這個就很奇怪了......mybatis

  通過前臺調試發現根本沒有前臺根本沒有發送Token到服務端, 只發送了一個 Access-Control-Allow-Headers:token-key 。因此我就覺得是前臺添加token的時候有問題,翻來覆去換了好多種添加header的方法,依然不行。可是很奇怪啊明明他把header的key "token-key" 發送了, 爲何不發送值呢。仔細又去看了CROS的介紹。原來CROS複雜請求時會先發送一個OPTIONS請求,來測試服務器是否支持本次請求,這個請求時不帶數據的,請求成功後纔會發送真實的請求。因此前面那個只發送key的問題是要確認服務器支不支持接收這個header。因此每次獲取不到數據的請求都是OPTIONS請求?。因此咱們要作的就是把全部的OPTIONS請求通通放行。app

  作法是在 JWTOrAuthenticationFilter 中重寫一個 preHandler 方法。 代碼以下:

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
       HttpServletRequest httpRequest = WebUtils.toHttp(request);
       HttpServletResponse httpResponse = WebUtils.toHttp(response);
       if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
           httpResponse.setHeader("Access-control-Allow-Origin", origin);
           httpResponse.setHeader("Access-Control-Allow-Methods", httpRequest.getMethod());
           httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));
           httpResponse.setStatus(HttpStatus.OK.value());
           return false;
       }
       return super.preHandle(request, response);
   }
  • 前臺Response數據的問題

  上一步完成後,第一次OPTIONS請求成功,第二次真實請求也發送成功(狀態爲200),可是response裏面沒有數據。這就很尷尬了,調試代碼發現數據查詢成功,返回成功。可是前臺就是沒有數據, 並報錯(No 'Access-Control-Allow-Origin' header is present on the requested resource)根據這個錯誤去查看第二次請求的 response headers 果真發現全部跨域相關的header都沒了(Access-Control-Allow-sth),可是明明在Application添加了容許跨域的配置。這裏好像失效了,因而換了種方法,加了個CrosFilter, 終於解決問題。。

package com.webapp.web.filter;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component
    public class CorsFilter implements Filter {
    
         @Value("${cors.origin}")
         private String origin;
        
         @Override
         public void init(FilterConfig filterConfig) throws ServletException {
        
         }
        
         @Override
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
             HttpServletResponse httpResponse = (HttpServletResponse) response;
             HttpServletRequest httpRequest = (HttpServletRequest) request;
             httpResponse.setHeader("Access-Control-Allow-Origin", origin);
             httpResponse.setHeader("Access-Control-Allow-Methods", httpRequest.getMethod());
             httpResponse.setHeader("Access-Control-Max-Age", "3600");
             httpResponse.setHeader("Access-Control-Allow-Headers", httpRequest.getHeader("Access-Control-Request-Headers"));
             chain.doFilter(request, response);
         }
        
         @Override
         public void destroy() {
        
         }
    }

  雖然Application裏的配置爲何失效,暫時還沒弄明白是爲何,但是程序終因而能夠跑通了,撒花。。。。。??。下一步想弄明白Application中的配置爲何會失效

@Bean
public class CorsConfigurerAdapter extends WebMvcConfigurerAdapter{ 
    @Override public void addCorsMappings(CorsRegistry registry) {             
          registry.addMapping("*").allowedOrigins(origin); 
    } 
}
相關文章
相關標籤/搜索