Spring Security 自定義短信登錄認證的實現
自定義登錄filter
上篇文章我們說到,對於用戶的登錄,security通過定義一個filter攔截login路徑來實現的,所以我們要實現自定義登錄,需要自己定義一個filter,繼承AbstractAuthenticationProcessingFilter,從request中提取到手機號和驗證碼,然後提交給AuthenticationManager:
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_PHONE_KEY = "phone"; public static final String SPRING_SECURITY_FORM_VERIFY_CODE_KEY = "verifyCode"; private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/smsLogin", "POST"); protected SmsAuthenticationFilter() { super(DEFAULT_ANT_PATH_REQUEST_MATCHER); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { String phone = request.getParameter(SPRING_SECURITY_FORM_PHONE_KEY); String verifyCode = request.getParameter(SPRING_SECURITY_FORM_VERIFY_CODE_KEY); if (StringUtils.isBlank(phone)){ phone = ""; } if (StringUtils.isBlank(verifyCode)){ verifyCode = ""; } SmsAuthenticationToken authenticationToken = new SmsAuthenticationToken(phone, verifyCode); setDetails(request,authenticationToken); return getAuthenticationManager().authenticate(authenticationToken); } protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } }
其中SmsAuthenticationToken參照UsernamePasswordAuthenticationToken來實現:
public class SmsAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; private Object credentials; public SmsAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; //初始化完成,但是還未認證 setAuthenticated(false); } public SmsAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object principal, Object credentials) { super(authorities); this.principal = principal; this.credentials = credentials; setAuthenticated(true); } @Override public Object getCredentials() { return credentials; } @Override public Object getPrincipal() { return principal; } }
自定義provider實現身份認證
我們知道AuthenticationManager最終會委托給Provider來實現身份驗證,所以我們要判斷驗證碼是否正確,需要自定義Provider:
@Slf4j @Component public class SmsAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; @Override public Authentication authenticate(Authentication authentication) { Assert.isInstanceOf(SmsAuthenticationToken.class, authentication, () -> "SmsAuthenticationProvider.onlySupports Only SmsAuthenticationToken is supported"); SmsAuthenticationToken authenticationToken = (SmsAuthenticationToken) authentication; String phone = (String) authenticationToken.getPrincipal(); String verifyCode = (String) authenticationToken.getCredentials(); UserDetails userDetails = userDetailsService.loadUserByUsername(phone); if (userDetails == null){ throw new InternalAuthenticationServiceException("cannot get user info"); } //驗證碼是否正確 if (!StringUtils.equals(CacheUtil.getValue(phone),verifyCode)){ throw new AuthenticationCredentialsNotFoundException("驗證碼錯誤"); } return new SmsAuthenticationToken(userDetails.getAuthorities(),userDetails,verifyCode); } @Override public boolean supports(Class<?> authentication) { return authentication.isAssignableFrom(SmsAuthenticationToken.class); } }
上面的CacheUtil是封裝的guava cache的實現,模擬發送驗證碼存儲到內存中,在這個地方取出來做對比,如果對比失敗就拋異常,對比成功就返回一個新的token,這個token中是包含瞭用戶具有的權限的。
@Slf4j public class CacheUtil { private static final LoadingCache<String, String> CACHE = CacheBuilder.newBuilder() //基於容量回收:總數量100個 .maximumSize(100) //定時回收:沒有寫訪問1分鐘後失效清理 .expireAfterWrite(1, TimeUnit.MINUTES) //當在緩存中未找到所需的緩存項時,會執行CacheLoader的load方法加載緩存 .build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { log.debug("沒有找到緩存: {}",key); return ""; } }); public static void putValue(String key, String value){ CACHE.put(key,value); } public static String getValue(String key){ try { return CACHE.get(key); } catch (ExecutionException e) { e.printStackTrace(); } return ""; } }
身份認證結果回調
filter將手機號和驗證碼交給provider做驗證,經過provider的校驗,結果無非就兩種,一種驗證成功,一種驗證失敗,對於這兩種不同的結果,我們需要實現兩個handler,在獲取到結果之後做回調。因為我們這兒隻是簡單的做url跳轉,所以隻需要繼承SimpleUrlAuthenticationSuccessHandler:
對於success的:
@Component public class SmsAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { public SmsAuthSuccessHandler() { super("/index"); } }
對於failure的:
@Component public class SmsAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler { public SmsAuthFailureHandler() { super("/failure"); } }
上面整個登錄流程的組件就完成瞭,接下來需要將它們整合起來。
整合登錄組件
具體怎麼整合,我們可以參考表單登錄中,UsernamePasswordAuthenticationFilter是怎麼整合進去的,回到配置類,還記得我們是怎麼配置Security的嗎:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login") //登錄頁面 .successForwardUrl("/index") //登錄成功後的頁面 .failureForwardUrl("/failure") //登錄失敗後的頁面 .and() // 設置URL的授權 .authorizeRequests() // 這裡需要將登錄頁面放行 .antMatchers("/login") .permitAll() //除瞭上面,其他所有請求必須被認證 .anyRequest() .authenticated() .and() // 關閉csrf .csrf().disable(); } }
分析表單登錄實現
看第一句,調用瞭http.formLogin(),在HttpSecurity的formLogin方法定義如下:
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception { return getOrApply(new FormLoginConfigurer<>()); } private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer) throws Exception { //註意這個configure為SecurityConfigurerAdapter C existingConfig = (C) getConfigurer(configurer.getClass()); if (existingConfig != null) { return existingConfig; } return apply(configurer); }
apply方法為AbstractConfiguredSecurityBuilder中的方法,我們目前先不關註它的實現,後面會仔細展開講。現在隻需要知道通過這個方法就能將configurer加入到security配置中。
這個地方添加瞭一個FormLoginConfigurer類,對於這個類官方給的解釋為:
Adds form based authentication. All attributes have reasonable defaults making all parameters are optional. If no {@link #loginPage(String)} is specified, a default login page will be generated by the framework.
翻譯過來就是:
添加基於表單的身份驗證。所有屬性都有合理的默認值,從而使所有參數都是可選的。如果未指定loginPage,則框架將生成一個默認的登錄頁面。
看一下它的構造方法:
public FormLoginConfigurer() { super(new UsernamePasswordAuthenticationFilter(), null); usernameParameter("username"); passwordParameter("password"); }
發現UsernamePasswordAuthenticationFilter被傳遞給瞭父類,我們去它的父類AbstractAuthenticationFilterConfigurer看一下:
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<T, B> { protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) { this(); //這個filter就是UsernamePasswordAuthenticationFilter this.authFilter = authenticationFilter; if (defaultLoginProcessingUrl != null) { loginProcessingUrl(defaultLoginProcessingUrl); } } @Override public void configure(B http) throws Exception { PortMapper portMapper = http.getSharedObject(PortMapper.class); if (portMapper != null) { this.authenticationEntryPoint.setPortMapper(portMapper); } RequestCache requestCache = http.getSharedObject(RequestCache.class); if (requestCache != null) { this.defaultSuccessHandler.setRequestCache(requestCache); } //通過getSharedObject獲取共享對象。這裡獲取到AuthenticationManager this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); //設置成功和失敗的回調 this.authFilter.setAuthenticationSuccessHandler(this.successHandler); this.authFilter.setAuthenticationFailureHandler(this.failureHandler); if (this.authenticationDetailsSource != null) { this.authFilter.setAuthenticationDetailsSource(this.authenticationDetailsSource); } SessionAuthenticationStrategy sessionAuthenticationStrategy = http .getSharedObject(SessionAuthenticationStrategy.class); if (sessionAuthenticationStrategy != null) { this.authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy); } RememberMeServices rememberMeServices = http.getSharedObject(RememberMeServices.class); if (rememberMeServices != null) { this.authFilter.setRememberMeServices(rememberMeServices); } F filter = postProcess(this.authFilter); //添加filter http.addFilter(filter); } }
可以看到這個地方主要做瞭三件事:
- 將AuthenticationManager設置到filter中
- 添加成功/失敗的回調
- 將過濾器添加到過濾器鏈中
仿照表單登錄,實現配置類
仿照上面的三個步驟,我們可以自己實現一個配置類,查看AbstractAuthenticationFilterConfigurer的類繼承關系:
它最上面的頂級父類為SecurityConfigurerAdapter,我們就繼承它來實現我們基本的配置就行瞭(也可以繼承AbstractHttpConfigurer,沒有歧視的意思),並且實現上面的三步:
@Component public class SmsAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private SmsAuthSuccessHandler smsAuthSuccessHandler; @Autowired private SmsAuthFailureHandler smsAuthFailureHandler; @Autowired private SmsAuthenticationProvider smsAuthenticationProvider; @Override public void configure(HttpSecurity builder) throws Exception { SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter(); smsAuthenticationFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class)); smsAuthenticationFilter.setAuthenticationSuccessHandler(smsAuthSuccessHandler); smsAuthenticationFilter.setAuthenticationFailureHandler(smsAuthFailureHandler); builder.authenticationProvider(smsAuthenticationProvider); builder.addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
和上面有一點不同,我們自定義的filter需要指定一下順序,通過addFilterAfter方法將我們的filter添加到過濾器鏈中,並且將自定義的provider也一並配置瞭進來。
添加配置到security中
這樣我們的所有組件就已經組合到一起瞭,修改一下配置類:
@Autowired private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .loginPage("/login") .and() .apply(smsAuthenticationSecurityConfig) .and() // 設置URL的授權 .authorizeRequests() // 這裡需要將登錄頁面放行 .antMatchers("/login","/verifyCode","/smsLogin","/failure") .permitAll() // anyRequest() 所有請求 authenticated() 必須被認證 .anyRequest() .authenticated() .and() // 關閉csrf .csrf().disable(); }
再修改一下登錄頁面的登錄接口和字段名:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="/smsLogin" method="post"> <input type="text" name="phone"/> <input type="password" name="verifyCode"/> <input type="submit" value="提交"/> </form> </body> </html>
這樣通過短信驗證碼登錄的功能就已經實現瞭。
建議大傢可以自己重新實現一個自定義郵箱驗證碼登錄,加深映像。
源碼分析
configurer配置類工作原理
上面隻是簡單的使用,接下來我們分析configure是如何工作的。
大傢註意自己要打開idea跟著過一遍源碼
其實通過上面的配置我們可以發現,在security中的過濾器其實都是通過各種xxxConfigure來進行配置的,我們可以簡單的理解為filter就是和配置類綁定在一起的。明白瞭這個概念,我們繼續往下分析。
看上面AbstractAuthenticationFilterConfigurer的類繼承關系圖,從最上面開始分析,SecurityBuilder和SecurityConfigurer都是接口:
public interface SecurityBuilder<O> { /** * 構建一個對象並返回 */ O build() throws Exception; } public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> { /** * 初始化 */ void init(B builder) throws Exception; void configure(B builder) throws Exception; }
SecurityConfigurerAdapter分析
上面兩個接口的具體實現交給瞭SecurityConfigurerAdapter,在spring security中很多配置類都是繼承自SecurityConfigurerAdapter來實現的。看一下實現類SecurityConfigurerAdapter的源碼:
public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> { private B securityBuilder; private CompositeObjectPostProcessor objectPostProcessor = new CompositeObjectPostProcessor(); @Override public void init(B builder) throws Exception { } @Override public void configure(B builder) throws Exception { } /** * 返回SecurityBuilder,這樣就可以進行鏈式調用瞭 */ public B and() { return getBuilder(); } /** * 獲取到SecurityBuilder */ protected final B getBuilder() { Assert.state(this.securityBuilder != null, "securityBuilder cannot be null"); return this.securityBuilder; } /** * 執行對象的後置處理。默認值為委派給ObjectPostProcessor完成 * @return 可使用的已修改對象 */ @SuppressWarnings("unchecked") protected <T> T postProcess(T object) { return (T) this.objectPostProcessor.postProcess(object); } public void addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) { this.objectPostProcessor.addObjectPostProcessor(objectPostProcessor); } public void setBuilder(B builder) { this.securityBuilder = builder; } /** * ObjectPostProcessor的一個實現 */ private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> { private List<ObjectPostProcessor<?>> postProcessors = new ArrayList<>(); @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public Object postProcess(Object object) { //執行後置處理器的postProcess方法 for (ObjectPostProcessor opp : this.postProcessors) { Class<?> oppClass = opp.getClass(); Class<?> oppType = GenericTypeResolver.resolveTypeArgument(oppClass, ObjectPostProcessor.class); if (oppType == null || oppType.isAssignableFrom(object.getClass())) { object = opp.postProcess(object); } } return object; } //在list中添加瞭一個後置處理器 private boolean addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) { boolean result = this.postProcessors.add(objectPostProcessor); this.postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE); return result; } } }
嗯。。。這兩個方法都是空實現,應該是交給後面的子類去自己重寫方法。多出來的內容就隻是初始化瞭CompositeObjectPostProcessor,並基於它封裝瞭兩個方法。
CompositeObjectPostProcessor是ObjectPostProcessor的一個實現,ObjectPostProcessor實際上是一個後置處理器。
其次addObjectPostProcessor方法實際上就是在list中添加瞭一個後置處理器並排序。然後在postProcess方法中對這個list遍歷,判斷ObjectPostProcessor泛型類型和傳過來的參數類型是否為父子關系,再次調用postProcess方法。
這個地方可能有點疑惑,為什麼要再調用一次postProcess,這不就成遞歸瞭嗎,我們註意一下CompositeObjectPostProcessor類是private的,也就是隻能在SecurityConfigurerAdapter內部使用,這裡再次調用postProcess方法應該是其他的ObjectPostProcessor的實現。
可以看一下ObjectPostProcessor總共有兩個實現,另外還有一個是AutowireBeanFactoryObjectPostProcessor:
final class AutowireBeanFactoryObjectPostProcessor implements ObjectPostProcessor<Object>, DisposableBean, SmartInitializingSingleton { private final Log logger = LogFactory.getLog(getClass()); private final AutowireCapableBeanFactory autowireBeanFactory; private final List<DisposableBean> disposableBeans = new ArrayList<>(); private final List<SmartInitializingSingleton> smartSingletons = new ArrayList<>(); AutowireBeanFactoryObjectPostProcessor(AutowireCapableBeanFactory autowireBeanFactory) { Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null"); this.autowireBeanFactory = autowireBeanFactory; } @Override @SuppressWarnings("unchecked") public <T> T postProcess(T object) { if (object == null) { return null; } T result = null; try { result = (T) this.autowireBeanFactory.initializeBean(object, object.toString()); } catch (RuntimeException ex) { Class<?> type = object.getClass(); throw new RuntimeException("Could not postProcess " + object + " of type " + type, ex); } this.autowireBeanFactory.autowireBean(object); if (result instanceof DisposableBean) { this.disposableBeans.add((DisposableBean) result); } if (result instanceof SmartInitializingSingleton) { this.smartSingletons.add((SmartInitializingSingleton) result); } return result; } @Override public void afterSingletonsInstantiated() { for (SmartInitializingSingleton singleton : this.smartSingletons) { singleton.afterSingletonsInstantiated(); } } @Override public void destroy() { for (DisposableBean disposable : this.disposableBeans) { try { disposable.destroy(); } catch (Exception ex) { this.logger.error(ex); } } } }
這裡面主要是通過autowireBeanFactory將對象註入到容器當中,在security中,很多對象都是new出來的,這些new出來的對象和容器沒有任何關聯,也不方便管理,所以通過AutowireBeanFactoryObjectPostProcessor來完成對象的註入。
也就是說,在SecurityConfigurerAdapter中定義的這兩個方法,其實就是將對象放進spring容器當中,方便管理。
AbstractConfiguredSecurityBuilder分析
SecurityConfigurerAdapter的內容就這麼多瞭,繼續往下看AbstractHttpConfigurer:
public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>> extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> { @SuppressWarnings("unchecked") public B disable() { getBuilder().removeConfigurer(getClass()); return getBuilder(); } @SuppressWarnings("unchecked") public T withObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) { addObjectPostProcessor(objectPostProcessor); return (T) this; } }
代碼很少,第二個方法就是調用SecurityConfigurerAdapter的方法,這裡主要看第一個disable方法,我們在配置類中就已經使用過瞭, 在禁用csrf的時候調用瞭 csrf().disable(),就是通過這個方法,將csrf的配置移除瞭。
繼續看disable方法是調用瞭AbstractConfiguredSecurityBuilder中的removeConfigurer方法,實際上就是移除LinkedHashMap中的一個元素:
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>(); public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) { List<C> configs = (List<C>) this.configurers.remove(clazz); if (configs == null) { return new ArrayList<>(); } return new ArrayList<>(configs); }
既然有移除的方法,那肯定就有添加的方法:
private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>(); private final Map<Class<?>, Object> sharedObjects = new HashMap<>(); @SuppressWarnings("unchecked") private <C extends SecurityConfigurer<O, B>> void add(C configurer) { Assert.notNull(configurer, "configurer cannot be null"); Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer .getClass(); synchronized (this.configurers) { if (this.buildState.isConfigured()) { throw new IllegalStateException("Cannot apply " + configurer + " to already built object"); } List<SecurityConfigurer<O, B>> configs = null; if (this.allowConfigurersOfSameType) { configs = this.configurers.get(clazz); } configs = (configs != null) ? configs : new ArrayList<>(1); configs.add(configurer); this.configurers.put(clazz, configs); if (this.buildState.isInitializing()) { this.configurersAddedInInitializing.add(configurer); } } }
我們自定義短信登錄的時候,在配置類中添加自定義配置: .apply(smsAuthenticationSecurityConfig),這個apply方法實際上就是調用上面的方法,將配置添加瞭進去。
既然配置都添加到這個容器當中瞭,那什麼時候取出來用呢:
private Collection<SecurityConfigurer<O, B>> getConfigurers() { List<SecurityConfigurer<O, B>> result = new ArrayList<>(); for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) { result.addAll(configs); } return result; } //執行所有configurer的初始化方法 private void init() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.init((B) this); } for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) { configurer.init((B) this); } } //獲取到所有的configure,遍歷執行configure方法 private void configure() throws Exception { //從LinkedHashMap中獲取到configurer Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.configure((B) this); } }
在init和configure方法中,調用瞭配置類的configure方法,到這裡其實整個流程就已經通瞭。
我們一般自定義登錄,都會實現這個configure方法,在這個方法裡初始化一個filter,然後加入到過濾器鏈中。
而這個類的init和configure方法,實際上是在調用SecurityBuilder 的build方法被調用的,具體的代碼鏈路就不說瞭,大傢感興趣的可以自己去看一下。
最後貼一下AbstractConfiguredSecurityBuilder的所有代碼(已精簡):
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> { private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>(); private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>(); private final Map<Class<?>, Object> sharedObjects = new HashMap<>(); private final boolean allowConfigurersOfSameType; private ObjectPostProcessor<Object> objectPostProcessor; @SuppressWarnings("unchecked") public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception { configurer.addObjectPostProcessor(this.objectPostProcessor); configurer.setBuilder((B) this); add(configurer); return configurer; } public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception { add(configurer); return configurer; } @SuppressWarnings("unchecked") public <C> void setSharedObject(Class<C> sharedType, C object) { this.sharedObjects.put(sharedType, object); } @SuppressWarnings("unchecked") public <C> C getSharedObject(Class<C> sharedType) { return (C) this.sharedObjects.get(sharedType); } /** * Gets the shared objects * @return the shared Objects */ public Map<Class<?>, Object> getSharedObjects() { return Collections.unmodifiableMap(this.sharedObjects); } @SuppressWarnings("unchecked") private <C extends SecurityConfigurer<O, B>> void add(C configurer) { Assert.notNull(configurer, "configurer cannot be null"); Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer .getClass(); synchronized (this.configurers) { if (this.buildState.isConfigured()) { throw new IllegalStateException("Cannot apply " + configurer + " to already built object"); } List<SecurityConfigurer<O, B>> configs = null; if (this.allowConfigurersOfSameType) { configs = this.configurers.get(clazz); } configs = (configs != null) ? configs : new ArrayList<>(1); configs.add(configurer); this.configurers.put(clazz, configs); if (this.buildState.isInitializing()) { this.configurersAddedInInitializing.add(configurer); } } } /** * 通過class name移除相關的配置類 */ @SuppressWarnings("unchecked") public <C extends SecurityConfigurer<O, B>> List<C> removeConfigurers(Class<C> clazz) { List<C> configs = (List<C>) this.configurers.remove(clazz); if (configs == null) { return new ArrayList<>(); } return new ArrayList<>(configs); } /** * 通過class name移除相關的配置類 */ @SuppressWarnings("unchecked") public <C extends SecurityConfigurer<O, B>> C removeConfigurer(Class<C> clazz) { List<SecurityConfigurer<O, B>> configs = this.configurers.remove(clazz); if (configs == null) { return null; } Assert.state(configs.size() == 1, () -> "Only one configurer expected for type " + clazz + ", but got " + configs); return (C) configs.get(0); } @SuppressWarnings("unchecked") public B objectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null"); this.objectPostProcessor = objectPostProcessor; return (B) this; } protected <P> P postProcess(P object) { return this.objectPostProcessor.postProcess(object); } //執行所有configurer的初始化方法 private void init() throws Exception { Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.init((B) this); } for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) { configurer.init((B) this); } } //獲取到所有的configure,遍歷執行configure方法 private void configure() throws Exception { //從LinkedHashMap中獲取到configurer Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.configure((B) this); } } //執行鉤子函數和configure方法 protected final O doBuild() throws Exception { synchronized (this.configurers) { this.buildState = BuildState.INITIALIZING; beforeInit(); init(); this.buildState = BuildState.CONFIGURING; beforeConfigure(); configure(); this.buildState = BuildState.BUILDING; O result = performBuild(); this.buildState = BuildState.BUILT; return result; } } }
到此這篇關於Spring Security 自定義短信登錄認證的實現的文章就介紹到這瞭,更多相關SpringSecurity 短信登錄認證內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Spring boot整合security詳解
- Spring Security 過濾器註冊脈絡梳理
- Java 中泛型 T 和 ? 的區別詳解
- Spring Security動態權限的實現方法詳解
- Java中的BaseTypeHandler自定義類型轉換器的使用