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!

推薦閱讀: