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); 
    } 
}