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 短信登录认证内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Spring Security的formLogin登录认证模式

    一.formLogin的应用场景 在本专栏之前的文章中,已经给大家介绍过Spring Security的HttpBasic模式,该模式比较简单,只是进行了通过携带Http的Header进行简单的登录验证,而且没有定制的登录页面,所以使用场景比较窄. 对于一个完整的应用系统,与登录验证相关的页面都是高度定制化的,非常美观而且提供多种登录方式.这就需要Spring Security支持我们自己定制登录页面,也就是本文给大家介绍的formLogin模式登录认证模式. 准备工作 新建一个Spring B

  • SpringSecurity构建基于JWT的登录认证实现

    最近项目的登录验证部分,采用了 JWT 验证的方式.并且既然采用了 Spring Boot 框架,验证和权限管理这部分,就自然用了 Spring Security.这里记录一下具体实现. 在项目采用 JWT 方案前,有必要先了解它的特性和适用场景,毕竟软件工程里,没有银弹.只有合适的场景,没有万精油的方案. 一言以蔽之,JWT 可以携带非敏感信息,并具有不可篡改性.可以通过验证是否被篡改,以及读取信息内容,完成网络认证的三个问题:"你是谁"."你有哪些权限".&qu

  • Springboot+SpringSecurity+JWT实现用户登录和权限认证示例

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的后端框架是Spring-Security,该框架提供了一整套比较成熟,也很完整的机制用于处理各类场景下的可以基于权限,资源路径,以及授权方面的解决方案,部分模块支持定制化,而且在和oauth2.0进行了很好的无缝连接,在移动互联网的授权认证方面有很强的优势,具体的使用大家可以结合自己的业务场景进行选

  • spring security自定义认证登录的全过程记录

    spring security使用分类: 如何使用spring security,相信百度过的都知道,总共有四种用法,从简到深为: 1.不用数据库,全部数据写在配置文件,这个也是官方文档里面的demo: 2.使用数据库,根据spring security默认实现代码设计数据库,也就是说数据库已经固定了,这种方法不灵活,而且那个数据库设计得很简陋,实用性差: 3.spring security和Acegi不同,它不能修改默认filter了,但支持插入filter,所以根据这个,我们可以插入自己的f

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

    自定义登录filter 上篇文章我们说到,对于用户的登录,security通过定义一个filter拦截login路径来实现的,所以我们要实现自定义登录,需要自己定义一个filter,继承AbstractAuthenticationProcessingFilter,从request中提取到手机号和验证码,然后提交给AuthenticationManager: public class SmsAuthenticationFilter extends AbstractAuthenticationPro

  • Spring Security 实现短信验证码登录功能

    之前文章都是基于用户名密码登录,第六章图形验证码登录其实还是用户名密码登录,只不过多了一层图形验证码校验而已:Spring Security默认提供的认证流程就是用户名密码登录,整个流程都已经固定了,虽然提供了一些接口扩展,但是有些时候我们就需要有自己特殊的身份认证逻辑,比如用短信验证码登录,它和用户名密码登录的逻辑是不一样的,这时候就需要重新写一套身份认证逻辑. 开发短信验证码接口 获取验证码 短信验证码的发送获取逻辑和图片验证码类似,这里直接贴出代码. @GetMapping("/code/

  • Spring Security自定义登录页面认证过程常用配置

    目录 一.自定义登录页面 1.编写登录页面 2.修改配置类 3.编写控制器 二. 认证过程其他常用配置 1.失败跳转 1.1编写页面 1.2修改表单配置 1.3添加控制器方法 1.4设置fail.html不需要认证 2.设置请求账户和密码的参数名 2.1源码简介 2.2修改配置 2.3修改页面 3.自定义登录成功处理器 3.1源码分析 3.2代码实现 4.自定义登录失败处理器 4.1源码分析 4.2代码实现 一.自定义登录页面 虽然Spring Security给我们提供了登录页面,但是对于实际

  • Spring Security自定义认证逻辑实例详解

    目录 前言 分析问题 自定义 Authentication 自定义 Filter 自定义 Provider 自定义认证成功/失败后的 Handler 配置自定义认证的逻辑 测试 总结 前言 这篇文章的内容基于对Spring Security 认证流程的理解,如果你不了解,可以读一下这篇文章:Spring Security 认证流程 . 分析问题 以下是 Spring Security 内置的用户名/密码认证的流程图,我们可以从这里入手: 根据上图,我们可以照猫画虎,自定义一个认证流程,比如手机短

  • Spring Security自定义认证器的实现代码

    目录 Authentication AuthenticationProvider SecurityConfigurerAdapter UserDetailsService TokenFilter 登录过程 在了解过Security的认证器后,如果想自定义登陆,只要实现AuthenticationProvider还有对应的Authentication就可以了 Authentication 首先要创建一个自定义的Authentication,Security提供了一个Authentication的子

  • spring security自定义登录页面

    在项目中我们肯定不能使用Spring自己生成的登录页面,而要用我们自己的登录页面,下面讲一下如何自定义登录页面,先看下配置 <sec:http auto-config="true"> <sec:intercept-url pattern="/app.jsp" access="ROLE_SERVICE"/> <sec:intercept-url pattern="/**" access="

  • spring security自定义决策管理器

    首先介绍下Spring的决策管理器,其接口为AccessDecisionManager,抽象类为AbstractAccessDecisionManager.而我们要自定义决策管理器的话一般是继承抽象类而不去直接实现接口. 在Spring中引入了投票器(AccessDecisionVoter)的概念,有无权限访问的最终觉得权是由投票器来决定的,最常见的投票器为RoleVoter,在RoleVoter中定义了权限的前缀,先看下Spring在RoleVoter中是怎么处理授权的. public int

  • Java开发之spring security实现基于MongoDB的认证功能

    本文实例讲述了Java开发之spring security实现基于MongoDB的认证功能.分享给大家供大家参考,具体如下: spring security对基于数据库的认证支持仅限于JDBC,而很多项目并非使用JDBC,比如Nosql数据库很多使用的是 Mongo Java Driver,这样就无法用默认的<jdbc-user-service>进行支持认证. 如果项目不是使用JDBC,没么解决办法就是:自己定义一个认证服务. 新建一个CustomUserDetailsService类 这个类

  • Spring security 自定义过滤器实现Json参数传递并兼容表单参数(实例代码)

    依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g

随机推荐