c语言sscanf函数的用法是什么
257
2023-01-27
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
//基于容量回收:总数量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
return getOrApply(new FormLoginConfigurer<>());
}
private
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
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
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
@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
private final Log logger = LogFactory.getLog(getClass());
private final AutowireCapableBeanFactory autowireBeanFactory;
private final List
private final List
AutowireBeanFactoryObjectPostProcessor(AutowireCapableBeanFactory autowireBeanFactory) {
Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");
this.autowireBeanFactory = autowireBeanFactory;
}
@Override
@SuppressWarnings("unchecked")
public
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
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
public
List
if (configs == null) {
return new ArrayList<>();
}
return new ArrayList<>(configs);
}
既然有移除的方法,那肯定就有添加的方法:
private final List
private final Map
@SuppressWarnings("unchecked")
private
Assert.notNull(configurer, "configurer cannot be null");
Class extends SecurityConfigurer
.getClass();
synchronized (this.configurers) {
if (this.buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
}
List
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
List
for (List
result.addAll(configs);
}
return result;
}
//执行所有configurer的初始化方法
private void init() throws Exception {
Collection
for (SecurityConfigurer
configurer.init((B) this);
}
for (SecurityConfigurer
configurer.init((B) this);
}
}
//获取到所有的configure,遍历执行configure方法
private void configure() throws Exception {
//从LinkedHashMap中获取到configurer
Collection
for (SecurityConfigurer
configurer.configure((B) this);
}
}
在init和configure方法中,调用了配置类的configure方法,到这里其实整个流程就已经通了。
我们一般自定义登录,都会实现这个configure方法,在这个方法里初始化一个filter,然后加入到过滤器链中。
而这个类的init和configure方法,实际上是在调用SecurityBuilder 的build方法被调用的,具体的代码链路就不说了,大家感兴趣的可以自己去看一下。
最后贴一下AbstractConfiguredSecurityBuilder的所有代码(已精简):
public abstract class AbstractConfiguredSecurityBuilder
extends AbstractSecurityBuilder
private final LinkedHashMap
private final List
private final Map
private final boolean allowConfigurersOfSameType;
private ObjectPostProcessor
@SuppressWarnings("unchecked")
public
configurer.addObjectPostProcessor(this.objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}
public
add(configurer);
return configurer;
}
@SuppressWarnings("unchecked")
public
this.sharedObjects.put(sharedType, object);
}
@SuppressWarnings("unchecked")
public
return (C) this.sharedObjects.get(sharedType);
}
/**
* Gets the shared objects
* @return the shared Objects
*/
public Map
return Collections.unmodifiableMap(this.sharedObjects);
}
@SuppressWarnings("unchecked")
private
Assert.notNull(configurer, "configurer cannot be null");
Class extends SecurityConfigurer
.getClass();
synchronized (this.configurers) {
if (this.buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer + " to already built object");
}
List
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
if (configs == null) {
return new ArrayList<>();
}
return new ArrayList<>(configs);
}
/**
* 通过class name移除相关的配置类
*/
@SuppressWarnings("unchecked")
public
List
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
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
for (SecurityConfigurer
configurer.init((B) this);
}
for (SecurityConfigurer
configurer.init((B) this);
}
}
//获取到所有的configure,遍历执行configure方法
private void configure() throws Exception {
//从LinkedHashMap中获取到configurer
Collection
for (SecurityConfigurer
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~