springboot情操陶冶-web配置(八)
本文關注應用的安全方面,涉及校驗以及授權方面,以springboot自帶的security板塊作為講解的內容
例項
建議使用者可直接路由至博主的先前部落格spring security整合cas方案。本文則針對相關的原始碼作下簡單的分析,方便筆者以及讀者更深入的瞭解spring的security板塊
@EnableWebSecurity
這個註解很精髓,基本上可以作為security的入口,筆者貼一下它的原始碼
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { /** * Controls debugging support for Spring Security. Default is false. * @return if true, enables debug support with Spring Security */ boolean debug() default false; }
可以分為三個部分來分析,
SpringWebMvcImportSelector
-支援mvc的引數安全校驗,替代了@EnableWebMvcSecurity註解
WebSecurityConfiguration
-Web的安全配置
@EnableGlobalAuthentication
-支援公共的認證校驗
SpringWebMvcImportSelector
首先先看下其如何整合mvc的安全校驗,其是一個ImportSelector介面,觀察下其複寫的方法
public String[] selectImports(AnnotationMetadata importingClassMetadata) { boolean webmvcPresent = ClassUtils.isPresent( "org.springframework.web.servlet.DispatcherServlet", getClass().getClassLoader()); return webmvcPresent ? new String[] { "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" } : new String[] {}; }
由上述程式碼可知,在classpath環境中存在mvc的關鍵類DispatcherServlet時便會引入WebMvcSecurityConfiguration類,那麼此類又配置了什麼東西呢?
裡面的程式碼很簡單,但關鍵是其是WebMvcConfigurer介面的實現類,根據之前的文章提到,該介面主要是用於配置MVC的相關功能,比如引數處理器、返回值處理器、異常處理器等等。
而該類只是擴充套件了相應的引數處理器,我們可以看下原始碼
@Override @SuppressWarnings("deprecation") public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { // 支援@AuthenticationPrinciple引數註解校驗 AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver(); authenticationPrincipalResolver.setBeanResolver(beanResolver); argumentResolvers.add(authenticationPrincipalResolver); // 廢棄 argumentResolvers .add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver()); // csrf token引數 argumentResolvers.add(new CsrfTokenArgumentResolver()); }
針對@AuthenticationPrinciple
註解的引數校驗,本文不展開了,這裡作下歸納
- 帶有@AuthenticationPrinciple註解的引數其值會從SecurityContext的上下文讀取相應的Authentication校驗資訊
- 有一個要求,被該註解修飾的引數須同SecurityContext的上下文存放的Authentication資訊為同一介面,否則則會返回null。如果設定了errorOnInvalidType屬性為true,則會拋異常
- 綜上所述,該註解主要是方便將校驗通過的Token用於引數賦值,其它的作用也不是很大
@EnableGlobalAuthentication
再來分析下springboot-security的公共認證校驗是什麼概念,貼下原始碼
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
OK,直接進入相應的AuthenticationConfiguration類進行具體的分析
1.其引入了ObjectPostProcessorConfiguration配置用於建立AutowireBeanFactoryObjectPostProcessor類,作用應該是通過Spring上下文例項相應的實體類並註冊到bean工廠中
@Bean
public ObjectPostProcessor<Object> objectPostProcessor(
AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
2.建立基於密碼機制的認證管理器Bean,型別為DefaultPasswordEncoderAuthenticationManagerBuilder
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
// 密碼加密器
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
// 認證事件傳播器
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
// 預設的認證管理器
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
上述的密碼加密器支援多種方式的加密,比如bcrypt(預設)/ladp/md5/sha-1等,感興趣的讀者可自行閱讀。使用者也可多用此Bean作額外的擴充套件,例如官方建議的如下程式碼
@Configuration
@EnableGlobalAuthentication
public class MyGlobalAuthenticationConfiguration {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
.and().withUser("admin").password("password").roles("ADMIN,USER");
}
}
3.建立基於UserDetails的認證器,用於管理使用者的授權資訊
@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
return new InitializeUserDetailsBeanManagerConfigurer(context);
}
其會建立基於Datasource源的DaoAuthenticationProvider許可權校驗器,前提是ApplicationContext上下文存在UserDetailsServiceBean物件,否則會不建立。如果使用者想基於資料庫或者其他資料來源的可嘗試複寫UserDetailsService介面
@Configuration
public class DaoUserDetailsServiceConfig {
/**
* load user info by dao
*
* @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
*/
@Configuration
public static class DefaultUserDetailsService implements UserDetailsService {
private static final String DEFAULT_PASS = "defaultPass";
// admin authority
private Collection<? extends GrantedAuthority> adminAuthority;
@Resource
private PasswordEncoder defaultPasswordEncoder;
public DefaultUserDetailsService() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(authority);
adminAuthority = Collections.unmodifiableList(authorities);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userdetails = new User(username, defaultPasswordEncoder.encode(DEFAULT_PASS), adminAuthority);
return userdetails;
}
@Bean
public PasswordEncoder daoPasswordEncoder() {
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder;
}
}
}
4.建立AuthenticationProvider認證器,用於使用者資訊的校驗
@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
同第三點,只是它就配置簡單的AuthenticationProvider至相應的AuthenticationManagerBuilderBean中
所以綜上所述,@EnableGlobalAuthentication註解的主要目的是配置認證管理器,裡面包含了加密器以及相應的認證器
WebSecurityConfiguration
web方面的安全配置,筆者也根據載入的順序來進行分析
1.獲取WebSecurityConfigurer介面bean集合的AutowiredWebSecurityConfigurersIgnoreParents類
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
此Bean用於獲取所有註冊在bean工廠上的WebSecurityConfigurer介面,使用者也一般通過此介面的抽象類WebSecurityConfigurerAdapter來進行相應的擴充套件
2.設定Security的Filter過濾鏈配置,提前為建立過濾鏈作準備
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
// WebSecurity建立
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
// 根據@Order屬性排序
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
// 校驗Order對應的值,不允許相同,否則會丟擲異常
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
// 對排序過的SecurityConfigurer依次放入WebSecurity物件中
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
這裡便提一下,我們在繼承WebSecurityConfigurerAdapter抽象類的時候,記得在其頭上加上@Order屬性,並且保證值唯一
3.建立Security過濾鏈
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
// 如果使用者沒有配置WebSecurityConfigurer介面,則建立一個空的
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
// create Filter
return webSecurity.build();
}
看來Filter攔截器的配置是通過WebSecurity這個類來完成的,限於裡面的程式碼過於複雜,本文就不展開了,感興趣的讀者可以重點關注下此類。由此可以得出Springboot的安全校驗是通過過濾鏈的設計方式來完成的
4.URI許可權校驗Bean,其依賴於第三點的配置
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
return webSecurity.getPrivilegeEvaluator();
}
5.安全校驗表示式驗證Bean,其也依賴於第三點的配置,應該是與第四點搭配使用
@Bean
@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
return webSecurity.getExpressionHandler();
}
小結
Springboot整合的Security板塊內容很多,本文也展示不完,不過值得關注的是以下幾個方面
1)WebSecurity的個性化配置類,一般是複寫抽象介面WebSecurityConfigurerAdapter,再加上@EnableWebSecurity註解便可
2)AuthenticationManagerBuilder認證校驗器,重點關注其中的密碼校驗器,用於密碼的加密解密,預設使用bcrypt方式。如果使用者想通過其他資料來源獲取使用者資訊,可以關注UserDetailsService介面
3)WebSecurity類,此類是Springboot Security模組的核心類,具體的過濾鏈配置均是由此類得到的。讀者以及筆者應該對此加以關注