最近在弄一个后台用Spring boot、Shiro、Spring data mybatis,前台用Angular 4的项目.在做权限控制的时候后台一直获取不到前台获取的数据(username, password, token等)。记录一下解决过程。
首先为了解决密码登录和Token验证两种验证方法,所以自定义了一个JWTOrAuthenticationFilter来根据前台传来的数据判断选择哪一种登录方法()。
当然想象是美好的,定义完成后发现每次访问时传入JWTOrAuthenticationFilter的onAccessDenied时传入的Request数据都是空的,前台调试发现请求时数据是发送了的。
JWTOrAuthenticationFilter访问异常
首先配置了/login=anon期望访问正常登陆的时候不通过Shiro的过滤器,而是访问Controller中定义的login方法。但是调试发现登录时根本没有进入Controller中定义的login方法,而是直接进入了JWTOrAuthenticationFilter。
Google一下找到一篇帖子( )。原来将自定义Shiro Filter注册为Spring Bean时,会被自动注册到全局的ApplicationFilterChain中,这个自定义的Filter无论如何都会执行,所以/login=anon配置失效了。
修改办法:不显式的将 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()); Mapfilters = new HashMap<>(); filters.put("authc", new JWTOrAuthenticationFilter(origin)); filterFactoryBean.setFilters(filters); return filterFactoryBean; }
JS的CROS请求的异常
经过上面的修改,正常登录终于可以成功了,撒花。。。。 但是问题又来了。。登录是可以成功了, 其他请求发送Token验证时应该经过 JWTOrAuthenticationFilter 去调用Token的登录验证Realm了, 但是 JWTOrAuthenticationFilter 获取不到前台发送的Token, 也就是只能登录,其他什么都干不了......MDZZ。这个就很奇怪了......
经过前台调试发现根本没有前台根本没有发送Token到服务端, 只发送了一个 Access-Control-Allow-Headers:token-key 。所以我就以为是前台添加token的时候有问题,翻来复去换了好多种添加header的方法,依然不行。但是很奇怪啊明明他把header的key "token-key" 发送了, 为什么不发送值呢。仔细又去看了CROS的介绍。原来CROS复杂请求时会先发送一个OPTIONS请求,来测试服务器是否支持本次请求,这个请求时不带数据的,请求成功后才会发送真实的请求。所以前面那个只发送key的问题是要确认服务器支不支持接收这个header。所以每次获取不到数据的请求都是OPTIONS请求?。所以我们要做的就是把所有的OPTIONS请求统统放行。
做法是在 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中的配置为什么会失效
@Beanpublic class CorsConfigurerAdapter extends WebMvcConfigurerAdapter{ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("*").allowedOrigins(origin); } }