最近在弄一個後臺用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); } }