之前用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就是對於業務場景下的免登錄的url,都統一添加一個前綴,好比/open/xxxx,這樣就能夠固定死了spring
web.ignoring().antMatchers("/info","/health","/hystrix.stream","/open/**");
後續有免登錄的url,好比/share,那麼改爲/open/share這樣就不用從新啓動了xcode
方案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; } //...... }
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; }
這裏有幾個要點
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; } }
這樣就大功告成了,之後就無需從新啓動來配置免登錄url了。其實這個還能夠擴展一下,支持動態的權限配置,這個下次有機會再講一下。