Spring Security 自定義短信登錄認(rèn)證的實(shí)現(xiàn)
自定義登錄filter
上篇文章我們說到,對(duì)于用戶的登錄,security通過定義一個(gè)filter攔截login路徑來實(shí)現(xiàn)的,所以我們要實(shí)現(xiàn)自定義登錄,需要自己定義一個(gè)filter,繼承AbstractAuthenticationProcessingFilter,從request中提取到手機(jī)號(hào)和驗(yàn)證碼,然后提交給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來實(shí)現(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;
//初始化完成,但是還未認(rèn)證
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實(shí)現(xiàn)身份認(rèn)證
我們知道AuthenticationManager最終會(huì)委托給Provider來實(shí)現(xiàn)身份驗(yàn)證,所以我們要判斷驗(yà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");
}
//驗(yàn)證碼是否正確
if (!StringUtils.equals(CacheUtil.getValue(phone),verifyCode)){
throw new AuthenticationCredentialsNotFoundException("驗(yàn)證碼錯(cuò)誤");
}
return new SmsAuthenticationToken(userDetails.getAuthorities(),userDetails,verifyCode);
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(SmsAuthenticationToken.class);
}
}
上面的CacheUtil是封裝的guava cache的實(shí)現(xiàn),模擬發(fā)送驗(yàn)證碼存儲(chǔ)到內(nèi)存中,在這個(gè)地方取出來做對(duì)比,如果對(duì)比失敗就拋異常,對(duì)比成功就返回一個(gè)新的token,這個(gè)token中是包含了用戶具有的權(quán)限的。
@Slf4j
public class CacheUtil {
private static final LoadingCache<String, String> CACHE = CacheBuilder.newBuilder()
//基于容量回收:總數(shù)量100個(gè)
.maximumSize(100)
//定時(shí)回收:沒有寫訪問1分鐘后失效清理
.expireAfterWrite(1, TimeUnit.MINUTES)
//當(dāng)在緩存中未找到所需的緩存項(xiàng)時(shí),會(huì)執(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 "";
}
}
身份認(rèn)證結(jié)果回調(diào)
filter將手機(jī)號(hào)和驗(yàn)證碼交給provider做驗(yàn)證,經(jīng)過provider的校驗(yàn),結(jié)果無非就兩種,一種驗(yàn)證成功,一種驗(yàn)證失敗,對(duì)于這兩種不同的結(jié)果,我們需要實(shí)現(xiàn)兩個(gè)handler,在獲取到結(jié)果之后做回調(diào)。因?yàn)槲覀冞@兒只是簡(jiǎn)單的做url跳轉(zhuǎn),所以只需要繼承SimpleUrlAuthenticationSuccessHandler:
對(duì)于success的:
@Component
public class SmsAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
public SmsAuthSuccessHandler() {
super("/index");
}
}
對(duì)于failure的:
@Component
public class SmsAuthFailureHandler extends SimpleUrlAuthenticationFailureHandler {
public SmsAuthFailureHandler() {
super("/failure");
}
}
上面整個(gè)登錄流程的組件就完成了,接下來需要將它們整合起來。
整合登錄組件
具體怎么整合,我們可以參考表單登錄中,UsernamePasswordAuthenticationFilter是怎么整合進(jìn)去的,回到配置類,還記得我們是怎么配置Security的嗎:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login") //登錄頁面
.successForwardUrl("/index") //登錄成功后的頁面
.failureForwardUrl("/failure") //登錄失敗后的頁面
.and()
// 設(shè)置URL的授權(quán)
.authorizeRequests()
// 這里需要將登錄頁面放行
.antMatchers("/login")
.permitAll()
//除了上面,其他所有請(qǐng)求必須被認(rèn)證
.anyRequest()
.authenticated()
.and()
// 關(guān)閉csrf
.csrf().disable();
}
}
分析表單登錄實(shí)現(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 {
//注意這個(gè)configure為SecurityConfigurerAdapter
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
return apply(configurer);
}
apply方法為AbstractConfiguredSecurityBuilder中的方法,我們目前先不關(guān)注它的實(shí)現(xiàn),后面會(huì)仔細(xì)展開講?,F(xiàn)在只需要知道通過這個(gè)方法就能將configurer加入到security配置中。
這個(gè)地方添加了一個(gè)FormLoginConfigurer類,對(duì)于這個(gè)類官方給的解釋為:
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.
翻譯過來就是:
添加基于表單的身份驗(yàn)證。所有屬性都有合理的默認(rèn)值,從而使所有參數(shù)都是可選的。如果未指定loginPage,則框架將生成一個(gè)默認(rèn)的登錄頁面。
看一下它的構(gòu)造方法:
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();
//這個(gè)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獲取共享對(duì)象。這里獲取到AuthenticationManager
this.authFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
//設(shè)置成功和失敗的回調(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);
}
}
可以看到這個(gè)地方主要做了三件事:
- 將AuthenticationManager設(shè)置到filter中
- 添加成功/失敗的回調(diào)
- 將過濾器添加到過濾器鏈中
仿照表單登錄,實(shí)現(xiàn)配置類
仿照上面的三個(gè)步驟,我們可以自己實(shí)現(xiàn)一個(gè)配置類,查看AbstractAuthenticationFilterConfigurer的類繼承關(guān)系:

它最上面的頂級(jí)父類為SecurityConfigurerAdapter,我們就繼承它來實(shí)現(xiàn)我們基本的配置就行了(也可以繼承AbstractHttpConfigurer,沒有歧視的意思),并且實(shí)現(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);
}
}
和上面有一點(diǎn)不同,我們自定義的filter需要指定一下順序,通過addFilterAfter方法將我們的filter添加到過濾器鏈中,并且將自定義的provider也一并配置了進(jìn)來。
添加配置到security中
這樣我們的所有組件就已經(jīng)組合到一起了,修改一下配置類:
@Autowired
private SmsAuthenticationSecurityConfig smsAuthenticationSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login")
.and()
.apply(smsAuthenticationSecurityConfig)
.and()
// 設(shè)置URL的授權(quán)
.authorizeRequests()
// 這里需要將登錄頁面放行
.antMatchers("/login","/verifyCode","/smsLogin","/failure")
.permitAll()
// anyRequest() 所有請(qǐng)求 authenticated() 必須被認(rèn)證
.anyRequest()
.authenticated()
.and()
// 關(guān)閉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>
這樣通過短信驗(yàn)證碼登錄的功能就已經(jīng)實(shí)現(xiàn)了。
建議大家可以自己重新實(shí)現(xiàn)一個(gè)自定義郵箱驗(yàn)證碼登錄,加深映像。
源碼分析
configurer配置類工作原理
上面只是簡(jiǎn)單的使用,接下來我們分析configure是如何工作的。
大家注意自己要打開idea跟著過一遍源碼
其實(shí)通過上面的配置我們可以發(fā)現(xiàn),在security中的過濾器其實(shí)都是通過各種xxxConfigure來進(jìn)行配置的,我們可以簡(jiǎn)單的理解為filter就是和配置類綁定在一起的。明白了這個(gè)概念,我們繼續(xù)往下分析。
看上面AbstractAuthenticationFilterConfigurer的類繼承關(guān)系圖,從最上面開始分析,SecurityBuilder和SecurityConfigurer都是接口:
public interface SecurityBuilder<O> {
/**
* 構(gòu)建一個(gè)對(duì)象并返回
*/
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分析
上面兩個(gè)接口的具體實(shí)現(xiàn)交給了SecurityConfigurerAdapter,在spring security中很多配置類都是繼承自SecurityConfigurerAdapter來實(shí)現(xiàn)的。看一下實(shí)現(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,這樣就可以進(jìn)行鏈?zhǔn)秸{(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í)行對(duì)象的后置處理。默認(rèn)值為委派給ObjectPostProcessor完成
* @return 可使用的已修改對(duì)象
*/
@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的一個(gè)實(shí)現(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中添加了一個(gè)后置處理器
private boolean addObjectPostProcessor(ObjectPostProcessor<?> objectPostProcessor) {
boolean result = this.postProcessors.add(objectPostProcessor);
this.postProcessors.sort(AnnotationAwareOrderComparator.INSTANCE);
return result;
}
}
}
嗯。。。這兩個(gè)方法都是空實(shí)現(xiàn),應(yīng)該是交給后面的子類去自己重寫方法。多出來的內(nèi)容就只是初始化了CompositeObjectPostProcessor,并基于它封裝了兩個(gè)方法。
CompositeObjectPostProcessor是ObjectPostProcessor的一個(gè)實(shí)現(xiàn),ObjectPostProcessor實(shí)際上是一個(gè)后置處理器。
其次addObjectPostProcessor方法實(shí)際上就是在list中添加了一個(gè)后置處理器并排序。然后在postProcess方法中對(duì)這個(gè)list遍歷,判斷ObjectPostProcessor泛型類型和傳過來的參數(shù)類型是否為父子關(guān)系,再次調(diào)用postProcess方法。
這個(gè)地方可能有點(diǎn)疑惑,為什么要再調(diào)用一次postProcess,這不就成遞歸了嗎,我們注意一下CompositeObjectPostProcessor類是private的,也就是只能在SecurityConfigurerAdapter內(nèi)部使用,這里再次調(diào)用postProcess方法應(yīng)該是其他的ObjectPostProcessor的實(shí)現(xiàn)。
可以看一下ObjectPostProcessor總共有兩個(gè)實(shí)現(xiàn),另外還有一個(gè)是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將對(duì)象注入到容器當(dāng)中,在security中,很多對(duì)象都是new出來的,這些new出來的對(duì)象和容器沒有任何關(guān)聯(lián),也不方便管理,所以通過AutowireBeanFactoryObjectPostProcessor來完成對(duì)象的注入。
也就是說,在SecurityConfigurerAdapter中定義的這兩個(gè)方法,其實(shí)就是將對(duì)象放進(jìn)spring容器當(dāng)中,方便管理。
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;
}
}
代碼很少,第二個(gè)方法就是調(diào)用SecurityConfigurerAdapter的方法,這里主要看第一個(gè)disable方法,我們?cè)谂渲妙愔芯鸵呀?jīng)使用過了, 在禁用csrf的時(shí)候調(diào)用了 csrf().disable(),就是通過這個(gè)方法,將csrf的配置移除了。
繼續(xù)看disable方法是調(diào)用了AbstractConfiguredSecurityBuilder中的removeConfigurer方法,實(shí)際上就是移除LinkedHashMap中的一個(gè)元素:
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);
}
}
}
我們自定義短信登錄的時(shí)候,在配置類中添加自定義配置: .apply(smsAuthenticationSecurityConfig),這個(gè)apply方法實(shí)際上就是調(diào)用上面的方法,將配置添加了進(jìn)去。
既然配置都添加到這個(gè)容器當(dāng)中了,那什么時(shí)候取出來用呢:
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方法,到這里其實(shí)整個(gè)流程就已經(jīng)通了。
我們一般自定義登錄,都會(huì)實(shí)現(xiàn)這個(gè)configure方法,在這個(gè)方法里初始化一個(gè)filter,然后加入到過濾器鏈中。
而這個(gè)類的init和configure方法,實(shí)際上是在調(diào)用SecurityBuilder 的build方法被調(diào)用的,具體的代碼鏈路就不說了,大家感興趣的可以自己去看一下。
最后貼一下AbstractConfiguredSecurityBuilder的所有代碼(已精簡(jiǎn)):
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移除相關(guān)的配置類
*/
@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移除相關(guān)的配置類
*/
@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;
}
}
}
到此這篇關(guān)于Spring Security 自定義短信登錄認(rèn)證的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity 短信登錄認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot+Spring Security實(shí)現(xiàn)前后端分離登錄認(rèn)證及權(quán)限控制的示例代碼
- Java SpringSecurity+JWT實(shí)現(xiàn)登錄認(rèn)證
- SpringBoot security安全認(rèn)證登錄的實(shí)現(xiàn)方法
- SpringSecurity實(shí)現(xiàn)前后端分離登錄token認(rèn)證詳解
- Springboot整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證和鑒權(quán)全過程
- springsecurity實(shí)現(xiàn)用戶登錄認(rèn)證快速使用示例代碼(前后端分離項(xiàng)目)
- Spring Security實(shí)現(xiàn)登錄認(rèn)證實(shí)戰(zhàn)教程
- SpringSecurity 自定義認(rèn)證登錄的項(xiàng)目實(shí)踐
- spring security登錄認(rèn)證授權(quán)的項(xiàng)目實(shí)踐
相關(guān)文章
Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡(jiǎn)單使用方法
本文主要介紹了Mybatis中兼容多數(shù)據(jù)源的databaseId(databaseIdProvider)的簡(jiǎn)單使用方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
IDEA中request.getParameter爆紅問題及解決
這篇文章主要介紹了IDEA中request.getParameter爆紅問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
IDEA2020.1啟動(dòng)SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在
這篇文章主要介紹了IDEA2020.1啟動(dòng)SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java中zip的壓縮和解壓縮的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java中zip的壓縮和解壓縮的實(shí)現(xiàn)代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02

