最近在弄一个后台用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一下找到一篇帖子( http://www.hillfly.com/2017/179.html)。原来将自定义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());
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。这个就很奇怪了......
经过前台调试发现根本没有前台根本没有发送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中的配置为什么会失效
@Bean
public class CorsConfigurerAdapter extends WebMvcConfigurerAdapter{
@Override public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("*").allowedOrigins(origin);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。