Spring Security 自定义短信登录认证的实现

网友投稿 202 2022-11-01


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 CACHE = CacheBuilder.newBuilder()

//基于容量回收:总数量100个

.maximumSize(100)

//定时回收:没有写访问1分钟后失效清理

.expireAfterWrite(1, TimeUnit.MINUTES)

//当在缓存中未找到所需的缓存项时,会执行CacheLoader的load方法加载缓存

.build(new CacheLoader() {

@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 formLogin() throws Exception {

return getOrApply(new FormLoginConfigurer<>());

}

private > C getOrApply(C configurer)

throws Exception {

//注意这个configure为SecurityConfigurerAdapter

C existingConfig = (C) getConfigurer(configurer.getClass());

if (existingConfig != null) {

return existingConfig;

}

returQTrHgtXn 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, T extends AbstractAuthenticationFilterConfigurer, F extends AbstractAuthenticationProcessingFilter>

extends AbstractHttpConfigurer {

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 {

@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();

}

再修改一下登录页面的登录接口和字段名:

这样通过短信验证码登录的功能就已经实现了。

建议大家可以自己重新实现一个自定义邮箱验证码登录,加深映像。

源码分析

configurer配置类工作原理

上面只是简单的使用,接下来我们分析configure是如何工作的。

大家注意自己要打开idea跟着过一遍源码

其实通过上面的配置我们可以发现,在security中的过滤器其实都是通过各种xxxConfigure来进行配置的,我们可以简单的理解为filter就是和配置类绑定在一起的。明白了这个概念,我们继续往下分析。

看上面AbstractAuthenticationFilterConfigurer的类继承关系图,从最上面开始分析,SecurityBuilder和SecurityConfigurer都是接口:

public interface SecurityBuilder {

/**

* 构建一个对象并返回

*/

O build() throws Exception;

}

public interface SecurityConfigurer> {

/**

* 初始化

*/

void init(B builder) throws Exception;

void configure(B builder) throws Exception;

}

SecurityConfigurerAdapter分析

上面两个接口的具体实现交给了SecurityConfigurerAdapter,在spring security中很多配置类都是继承自SecurityConfigurerAdapter来实现的。看一下实现类SecurityConfigurerAdapter的源码:

public abstract class SecurityConfigurerAdapter> implements SecurityConfigurer {

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 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 {

private List> 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, DisposableBean, SmartInitializingSingleton {

private final Log logger = LogFactory.getLog(getClass());

private final AutowireCapableBeanFactory autowireBeanFactory;

private final List disposableBeans = new ArrayList<>();

private final List smartSingletons = new ArrayList<>();

AutowireBeanFactoryObjectPostProcessor(AutowireCapableBeanFactory autowireBeanFactory) {

Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");

this.autowireBeanFactory = autowireBeanFactory;

}

@Override

@SuppressWarnings("unchecked")

public 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, B extends HttpSecurityBuilder>

extends SecurityConfigurerAdapter {

@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>, List>> configurers = new LinkedHashMap<>();

public > List removeConfigurers(Class clazz) {

List configs = (List) this.configurers.remove(clazz);

if (configs == null) {

return new ArrayList<>();

}

return new ArrayList<>(configs);

}

既然有移除的方法,那肯定就有添加的方法:

private final List> configurersAddedInInitializing = new ArrayList<>();

private final Map, Object> sharedObjects = new HashMap<>();

@SuppressWarnings("unchecked")

private > void add(C configurer) {

Assert.notNull(configurer, "configurer cannot be null");

Class extends SecurityConfigurer> clazz = (Class extends SecurityConfigurer>) configurer

.getClass();

synchronized (this.configurers) {

if (this.buildState.isConfigured()) {

throw new IllegalStateException("Cannot apply " + configurer + " to already built object");

}

List> 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> getConfigurers() {

List> result = new ArrayList<>();

for (List> configs : this.configurers.values()) {

result.addAll(configs);

}

return result;

}

//执行所有configurer的初始化方法

private void init() throws Exception {

Collection> configurers = getConfigurers();

for (SecurityConfigurer configurer : configurers) {

configurer.init((B) this);

}

for (SecurityConfigurer configurer : this.configurersAddedInInitializing) {

configurer.init((B) this);

}

}

//获取到所有的configure,遍历执行configure方法

private void configure() throws Exception {

//从LinkedHashMap中获取到configurer

Collection> configurers = getConfigurers();

for (SecurityConfigurer configurer : configurers) {

configurer.configure((B) this);

}

}

在init和configure方法中,调用了配置类的configure方法,到这里其实整个流程就已经通了。

我们一般自定义登录,都会实现这个configure方法,在这个方法里初始化一个filter,然后加入到过滤器链中。

而这个类的init和configure方法,实际上是在调用SecurityBuilder 的build方法被调用的,具体的代码链路就不说了,大家感兴趣的可以自己去看一下。

最后贴一下AbstractConfiguredSecurityBuilder的所有代码(已精简):

public abstract class AbstractConfiguredSecurityBuilder>

extends AbstractSecurityBuilder {

private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>();

private final List> configurersAddedInInitializing = new ArrayList<>();

private final Map, Object> sharedObjects = new HashMap<>();

private final boolean allowConfigurersOfSameType;

private ObjectPostProcessor objectPostProcessor;

@SuppressWarnings("unchecked")

public > C apply(C configurer) throws Exception {

configurer.addObjectPostProcessor(this.objectPostProcessor);

configurer.setBuilder((B) this);

add(configurer);

return configurer;

}

public > C apply(C configurer) throws Exception {

add(configurer);

return configurer;

}

@SuppressWarnings("unchecked")

public void setSharedObject(Class sharedType, C object) {

this.sharedObjects.put(sharedType, object);

}

@SuppressWarnings("unchecked")

public C getSharedObject(Class sharedType) {

return (C) this.sharedObjects.get(sharedType);

}

/**

* Gets the shared objects

* @return the shared Objects

*/

public Map, Object> getSharedObjects() {

return Collections.unmodifiableMap(this.sharedObjects);

}

@SuppressWarnings("unchecked")

private > void add(C configurer) {

Assert.notNull(configurer, "configurer cannot be null");

Class extends SecurityConfigurer> clazz = (Class extends SecurityConfigurer>) configurer

.getClass();

synchronized (this.configurers) {

if (this.buildState.isConfigured()) {

throw new IllegalStateException("Cannot apply " + configurer + " to already built object");

}

List> 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 > List removeConfigurers(Class clazz) {

List configs = (List) this.configurers.remove(clazz);

if (configs == null) {

return new ArrayList<>();

}

return new ArrayList<>(configs);

}

/**

* 通过class name移除相关的配置类

*/

@SuppressWarnings("unchecked")

public > C removeConfigurer(Class clazz) {

List> 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 objectPostProcessor) {

Assert.notNull(objectPostProcessor, "objectPostProcessor cannot be null");

this.objectPostProcessor = objectPostProcessor;

return (B) this;

}

protected

P postProcess(P object) {

return this.objectPostProcessor.postProcess(object);

}

//执行所有configurer的初始化方法

private void init() throws Exception {

Collection> configurers = getConfigurers();

for (SecurityConfigurer configurer : configurers) {

configurer.init((B) this);

}

for (SecurityConfigurer configurer : this.configurersAddedInInitializing) {

configurer.init((B) this);

}

}

//获取到所有的configure,遍历执行configure方法

private void configure() throws Exception {

//从LinkedHashMap中获取到configurer

Collection> configurers = getConfigurers();

for (SecurityConfigurer 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;

}

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:CSS和JavaScript以及Ajax实现预加载图片的方法及优缺点分析
下一篇:Java操作XML文件
相关文章

 发表评论

暂时没有评论,来抢沙发吧~