美文网首页spring cloud
spring security运行时配置ignore url

spring security运行时配置ignore url

作者: go4it | 来源:发表于2017-08-14 10:37 被阅读433次

    以前用shiro的比较多,不过spring boot倒是挺推崇自家的spring security的,有默认的starter,于是也就拿来用了。

    问题

    对于免登陆的url,采用java config,可以这样配置:

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        public void configure(WebSecurity web) throws Exception {
            //ignore
            web.ignoring().antMatchers("/info","/health","/hystrix.stream");
        }
    }
    

    但是这样配置有个缺点,就是每次要新增一个免登陆的url的时候,就得重新启动一遍,这个就不是太好了。有没有解决方案呢。

    方案1

    方案1就是对于业务场景下的免登陆的url,都统一添加一个前缀,比如/open/xxxx,这样就可以固定死了

    web.ignoring().antMatchers("/info","/health","/hystrix.stream","/open/**");
    

    后续有免登陆的url,比如/share,那么改成/open/share这样就不用重新启动了

    方案2

    方案2就是去hack一下spring security,这个需要了解一下spring security的运行机制:

    Spring Security 的底层是通过一系列的 Filter 来管理的,每个 Filter 都有其自身的功能,它们的顺序也是非常重要的。
    里头比较重要的一个就是名为SPRING_SECURITY_FILTER_CHAIN的FilterChainProxy,Security 将会为每一个 http 元素创建对应的 FilterChain(DefaultSecurityFilterChain),同时按照它们的声明顺序加入到 FilterChainProxy。所以当我们同时定义多个 http 元素时要确保将更具有特性的 URL 配置在前。

    public class FilterChainProxy extends GenericFilterBean {
        // ~ Static fields/initializers
        // =====================================================================================
    
        private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    
        // ~ Instance fields
        // ================================================================================================
    
        private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
                ".APPLIED");
    
        private List<SecurityFilterChain> filterChains;
    
        private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
    
        private HttpFirewall firewall = new DefaultHttpFirewall();
    
        // ~ Methods
        // ========================================================================================================
    
        public FilterChainProxy() {
        }
    
        public FilterChainProxy(SecurityFilterChain chain) {
            this(Arrays.asList(chain));
        }
    
        public FilterChainProxy(List<SecurityFilterChain> filterChains) {
            this.filterChains = filterChains;
        }
    
        //......
    
    }
    

    ignore的原理

    DefaultSecurityFilterChain的构造器如下
    spring-security-web-4.1.4.RELEASE-sources.jar!/org/springframework/security/web/DefaultSecurityFilterChain.java

    public final class DefaultSecurityFilterChain implements SecurityFilterChain {
        private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
        private final RequestMatcher requestMatcher;
        private final List<Filter> filters;
    
        public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
            this(requestMatcher, Arrays.asList(filters));
        }
    
        public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
            logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
            this.requestMatcher = requestMatcher;
            this.filters = new ArrayList<Filter>(filters);
        }
    }
    

    需要一个RequestMatcher以及作用在它上面的filter,如果你不传filter的话,那就是这个RequestMatcher对应的url就是免登陆的。spring security会根据FilterChainProxy中的filter chain的顺序去挨个匹配当前请求的url,然后执行对应的filter逻辑,在前面的优先匹配。

    思路

    要在运行时增加免登陆url的话,就需要运行时去修改FilterChainProxy中的filterChains,不过源码里头返回了不可变的集合
    4.1.4.RELEASE/spring-security-web-4.1.4.RELEASE-sources.jar!/org/springframework/security/web/FilterChainProxy.java

        /**
         * @return the list of {@code SecurityFilterChain}s which will be matched against and
         * applied to incoming requests.
         */
        public List<SecurityFilterChain> getFilterChains() {
            return Collections.unmodifiableList(filterChains);
        }
    

    因此这里需要hack一下,用反射去获取

    方案

        List<SecurityFilterChain> addIgnoreUrl(String url){
            FilterChainProxy obj = (FilterChainProxy) ApplicationContextHolder.getContext().getBean("springSecurityFilterChain");
            List<SecurityFilterChain> filterChains = (List<SecurityFilterChain>) ReflectUtil.getProperty(obj,"filterChains");
            filterChains.add(0,new DefaultSecurityFilterChain(new AntPathRequestMatcher(url, null)));
            return filterChains;
        }
    

    这里有几个要点

    • 反射获取可修改的filterChains
    public static Object getProperty(Object obj, String fieldName) {
            try {
                Field field = obj.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(obj);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    • 添加到filterChains的最前面使其优先匹配到
    • filter要设置为null,表示免登陆

    小结

    这样就大功告成了,以后就无需重新启动来配置免登陆url了。其实这个还可以扩展一下,支持动态的权限配置,这个下次有机会再讲一下。

    doc

    相关文章

      网友评论

        本文标题:spring security运行时配置ignore url

        本文链接:https://www.haomeiwen.com/subject/skayrxtx.html