spring security运行时配置ignore url

之前用shiro的比较多,不过spring boot却是挺推崇自家的spring security的,有默认的starter,因而也就拿来用了。html

问题

对于免登录的url,采用java config,能够这样配置:java

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

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

方案1

方案1就是对于业务场景下的免登录的url,都统一添加一个前缀,好比/open/xxxx,这样就能够固定死了spring

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

后续有免登录的url,好比/share,那么改为/open/share这样就不用从新启动了xcode

方案2

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

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

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.javathis

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

思路

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

/**
     * @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

相关文章
相关标签/搜索