spring security運行時配置ignore url

以前用shiro的比較多,不過spring boot倒是挺推崇自家的spring security的,有默認(rèn)的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就是對于業(yè)務(wù)場景下的免登陸的url,都統(tǒng)一添加一個前綴,比如/open/xxxx,這樣就可以固定死了

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

后續(xù)有免登陸的url,比如/share,那么改成/open/share這樣就不用重新啟動了

方案2

方案2就是去hack一下spring security,這個需要了解一下spring security的運行機制:

Spring Security 的底層是通過一系列的 Filter 來管理的,每個 Filter 都有其自身的功能,它們的順序也是非常重要的。
里頭比較重要的一個就是名為SPRING_SECURITY_FILTER_CHAIN的FilterChainProxy,Security 將會為每一個 http 元素創(chuàng)建對應(yīng)的 FilterChain(DefaultSecurityFilterChain),同時按照它們的聲明順序加入到 FilterChainProxy。所以當(dāng)我們同時定義多個 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的構(gòu)造器如下
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對應(yīng)的url就是免登陸的。spring security會根據(jù)FilterChainProxy中的filter chain的順序去挨個匹配當(dāng)前請求的url,然后執(zhí)行對應(yīng)的filter邏輯,在前面的優(yōu)先匹配。

思路

要在運行時增加免登陸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的最前面使其優(yōu)先匹配到
  • filter要設(shè)置為null,表示免登陸

小結(jié)

這樣就大功告成了,以后就無需重新啟動來配置免登陸url了。其實這個還可以擴展一下,支持動態(tài)的權(quán)限配置,這個下次有機會再講一下。

doc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容