Spring boot 和 Shiro 做后台跨域访问权限控制遇到的问题

2020年11月27日 74点热度 0条评论 来源: 我们在笑什么

  最近在弄一个后台用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); 
    } 
}
    原文作者:我们在笑什么
    原文地址: https://segmentfault.com/a/1190000010757321
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。