最近在弄一个后台用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); } }