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跳轉(zhuǎn),所以只需要繼承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),后面會仔細展開講。現(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-07
IDEA中request.getParameter爆紅問題及解決
這篇文章主要介紹了IDEA中request.getParameter爆紅問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
IDEA2020.1啟動SpringBoot項目出現(xiàn)java程序包:xxx不存在
這篇文章主要介紹了IDEA2020.1啟動SpringBoot項目出現(xiàn)java程序包:xxx不存在,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06

