Spring Security 自定義短信登錄認證的實現(xiàn)
自定義登錄filter
上篇文章我們說到,對于用戶的登錄,security通過定義一個filter攔截login路徑來實現(xiàn)的,所以我們要實現(xiàn)自定義登錄,需要自己定義一個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來實現(xiàn):
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實現(xiàn)身份認證
我們知道AuthenticationManager最終會委托給Provider來實現(xiàn)身份驗證,所以我們要判斷驗證碼是否正確,需要自定義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的實現(xiàn),模擬發(fā)送驗證碼存儲到內(nèi)存中,在這個地方取出來做對比,如果對比失敗就拋異常,對比成功就返回一個新的token,這個token中是包含了用戶具有的權限的。
@Slf4j public class CacheUtil { private static final LoadingCache<String, String> CACHE = CacheBuilder.newBuilder() //基于容量回收:總數(shù)量100個 .maximumSize(100) //定時回收:沒有寫訪問1分鐘后失效清理 .expireAfterWrite(1, TimeUnit.MINUTES) //當在緩存中未找到所需的緩存項時,會執(zhí)行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 ""; } }
身份認證結果回調(diào)
filter將手機號和驗證碼交給provider做驗證,經(jīng)過provider的校驗,結果無非就兩種,一種驗證成功,一種驗證失敗,對于這兩種不同的結果,我們需要實現(xiàn)兩個handler,在獲取到結果之后做回調(diào)。因為我們這兒只是簡單的做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(); } }
分析表單登錄實現(xiàn)
看第一句,調(diào)用了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中的方法,我們目前先不關注它的實現(xiàn),后面會仔細展開講?,F(xiàn)在只需要知道通過這個方法就能將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.
翻譯過來就是:
添加基于表單的身份驗證。所有屬性都有合理的默認值,從而使所有參數(shù)都是可選的。如果未指定loginPage,則框架將生成一個默認的登錄頁面。
看一下它的構造方法:
public FormLoginConfigurer() { super(new UsernamePasswordAuthenticationFilter(), null); usernameParameter("username"); passwordParameter("password"); }
發(fā)現(xiàn)UsernamePasswordAuthenticationFilter被傳遞給了父類,我們?nèi)ニ母割怉bstractAuthenticationFilterConfigurer看一下:
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)); //設置成功和失敗的回調(diào) 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中
- 添加成功/失敗的回調(diào)
- 將過濾器添加到過濾器鏈中
仿照表單登錄,實現(xiàn)配置類
仿照上面的三個步驟,我們可以自己實現(xiàn)一個配置類,查看AbstractAuthenticationFilterConfigurer的類繼承關系:
它最上面的頂級父類為SecurityConfigurerAdapter,我們就繼承它來實現(xiàn)我們基本的配置就行了(也可以繼承AbstractHttpConfigurer,沒有歧視的意思),并且實現(xiàn)上面的三步:
@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中
這樣我們的所有組件就已經(jīng)組合到一起了,修改一下配置類:
@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>
這樣通過短信驗證碼登錄的功能就已經(jīng)實現(xiàn)了。
建議大家可以自己重新實現(xiàn)一個自定義郵箱驗證碼登錄,加深映像。
源碼分析
configurer配置類工作原理
上面只是簡單的使用,接下來我們分析configure是如何工作的。
大家注意自己要打開idea跟著過一遍源碼
其實通過上面的配置我們可以發(fā)現(xiàn),在security中的過濾器其實都是通過各種xxxConfigure來進行配置的,我們可以簡單的理解為filter就是和配置類綁定在一起的。明白了這個概念,我們繼續(xù)往下分析。
看上面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分析
上面兩個接口的具體實現(xiàn)交給了SecurityConfigurerAdapter,在spring security中很多配置類都是繼承自SecurityConfigurerAdapter來實現(xiàn)的。看一下實現(xiàn)類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,這樣就可以進行鏈式調(diào)用了 */ public B and() { return getBuilder(); } /** * 獲取到SecurityBuilder */ protected final B getBuilder() { Assert.state(this.securityBuilder != null, "securityBuilder cannot be null"); return this.securityBuilder; } /** * 執(zhí)行對象的后置處理。默認值為委派給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的一個實現(xiàn) */ private static final class CompositeObjectPostProcessor implements ObjectPostProcessor<Object> { private List<ObjectPostProcessor<?>> postProcessors = new ArrayList<>(); @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public Object postProcess(Object object) { //執(zhí)行后置處理器的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; } } }
嗯。。。這兩個方法都是空實現(xiàn),應該是交給后面的子類去自己重寫方法。多出來的內(nèi)容就只是初始化了CompositeObjectPostProcessor,并基于它封裝了兩個方法。
CompositeObjectPostProcessor是ObjectPostProcessor的一個實現(xiàn),ObjectPostProcessor實際上是一個后置處理器。
其次addObjectPostProcessor方法實際上就是在list中添加了一個后置處理器并排序。然后在postProcess方法中對這個list遍歷,判斷ObjectPostProcessor泛型類型和傳過來的參數(shù)類型是否為父子關系,再次調(diào)用postProcess方法。
這個地方可能有點疑惑,為什么要再調(diào)用一次postProcess,這不就成遞歸了嗎,我們注意一下CompositeObjectPostProcessor類是private的,也就是只能在SecurityConfigurerAdapter內(nèi)部使用,這里再次調(diào)用postProcess方法應該是其他的ObjectPostProcessor的實現(xiàn)。
可以看一下ObjectPostProcessor總共有兩個實現(xiàn),另外還有一個是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出來的對象和容器沒有任何關聯(lián),也不方便管理,所以通過AutowireBeanFactoryObjectPostProcessor來完成對象的注入。
也就是說,在SecurityConfigurerAdapter中定義的這兩個方法,其實就是將對象放進spring容器當中,方便管理。
AbstractConfiguredSecurityBuilder分析
SecurityConfigurerAdapter的內(nèi)容就這么多了,繼續(xù)往下看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; } }
代碼很少,第二個方法就是調(diào)用SecurityConfigurerAdapter的方法,這里主要看第一個disable方法,我們在配置類中就已經(jīng)使用過了, 在禁用csrf的時候調(diào)用了 csrf().disable(),就是通過這個方法,將csrf的配置移除了。
繼續(xù)看disable方法是調(diào)用了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方法實際上就是調(diào)用上面的方法,將配置添加了進去。
既然配置都添加到這個容器當中了,那什么時候取出來用呢:
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; } //執(zhí)行所有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,遍歷執(zhí)行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方法中,調(diào)用了配置類的configure方法,到這里其實整個流程就已經(jīng)通了。
我們一般自定義登錄,都會實現(xiàn)這個configure方法,在這個方法里初始化一個filter,然后加入到過濾器鏈中。
而這個類的init和configure方法,實際上是在調(diào)用SecurityBuilder 的build方法被調(diào)用的,具體的代碼鏈路就不說了,大家感興趣的可以自己去看一下。
最后貼一下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); } //執(zhí)行所有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,遍歷執(zhí)行configure方法 private void configure() throws Exception { //從LinkedHashMap中獲取到configurer Collection<SecurityConfigurer<O, B>> configurers = getConfigurers(); for (SecurityConfigurer<O, B> configurer : configurers) { configurer.configure((B) this); } } //執(zhí)行鉤子函數(shù)和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 自定義短信登錄認證的實現(xiàn)的文章就介紹到這了,更多相關SpringSecurity 短信登錄認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- Springboot+Spring Security實現(xiàn)前后端分離登錄認證及權限控制的示例代碼
- Java SpringSecurity+JWT實現(xiàn)登錄認證
- SpringBoot security安全認證登錄的實現(xiàn)方法
- SpringSecurity實現(xiàn)前后端分離登錄token認證詳解
- Springboot整合SpringSecurity實現(xiàn)登錄認證和鑒權全過程
- springsecurity實現(xiàn)用戶登錄認證快速使用示例代碼(前后端分離項目)
- Spring Security實現(xiàn)登錄認證實戰(zhàn)教程
- SpringSecurity 自定義認證登錄的項目實踐
- spring security登錄認證授權的項目實踐
相關文章
Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法
本文主要介紹了Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡單使用方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-07-07IDEA中request.getParameter爆紅問題及解決
這篇文章主要介紹了IDEA中request.getParameter爆紅問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11IDEA2020.1啟動SpringBoot項目出現(xiàn)java程序包:xxx不存在
這篇文章主要介紹了IDEA2020.1啟動SpringBoot項目出現(xiàn)java程序包:xxx不存在,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06