1、Spring.factories
- • 从下图可以看出 spring-boot-autoconfigure/META-INF/spring.factories中关于SpringSecurity的自动配置类有以下这些
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
- • 但实际上,去除Reactive和Oauth2的配置类,只剩下下面三个
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
2、SecurityAutoConfiguration
- • 先从第一个自动配置类开始,主要是有两个作用:
- • 负责导入SpringBootWebSecurityConfiguration、 WebSecurityEnablerConfiguration、SecurityDataConfiguration(后面再说)
- • 在条件允许下往容器注册DefaultAuthenticationEventPublisher
@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
DefaultAuthenticationEventPublisher
- • 主要先看DefaultAuthenticationEventPublisher,作用如下
- • 认证成功后推送认证成功事件
- • 认证失败后推送认证失败事件
public class DefaultAuthenticationEventPublisher implements AuthenticationEventPublisher,
ApplicationEventPublisherAware {
private final Log logger = LogFactory.getLog(getClass());
private ApplicationEventPublisher applicationEventPublisher;
private final HashMap<String, Constructor<? extends AbstractAuthenticationEvent>> exceptionMappings = new HashMap<>();
public DefaultAuthenticationEventPublisher() {
this(null);
}
// 注册认证异常和对应事件的绑定关系
public DefaultAuthenticationEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
addMapping(BadCredentialsException.class.getName(),
AuthenticationFailureBadCredentialsEvent.class);
........
addMapping(
"org.springframework.security.authentication.cas.ProxyUntrustedException",
AuthenticationFailureProxyUntrustedEvent.class);
}
// 推送认证成功事件
public void publishAuthenticationSuccess(Authentication authentication) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(
authentication));
}
}
// 推送认证失败事件
public void publishAuthenticationFailure(AuthenticationException exception,
Authentication authentication) {
Constructor<? extends AbstractAuthenticationEvent> constructor = exceptionMappings
.get(exception.getClass().getName());
AbstractAuthenticationEvent event = null;
if (constructor != null) {
try {
event = constructor.newInstance(authentication, exception);
}
catch (IllegalAccessException | InvocationTargetException | InstantiationException ignored) {
}
}
if (event != null) {
if (applicationEventPublisher != null) {
applicationEventPublisher.publishEvent(event);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("No event was found for the exception "
+ exception.getClass().getName());
}
}
}
}
- • 使用场景:在ProviderManager中认证成功或失败推送对应的事件
- • ProviderManager是认证管理器,在UsernamePasswordAuthenticationFilter等过滤器中调用
3、UserDetailsServiceAutoConfiguration
- • 事实上我们可以在application.yml或者在application.yml中配置登录的用户名和密码然后就可以使用SpringSecurity,其原理也是因这里注入了一个InMemoryUserDetailsManager
- • 当然这种方式也是固定死了用户
- • 从第四行代码也能看出一旦容器中有AuthenticationManager、AuthenticationProvider、UserDetailsService(这三个是自定义认证或者说获取登录的用户的关键类),就不需要基于内存的InMemoryUserDetailsManager
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
// 要求没这三个的原因我估计是因为,如果有了这三个的话,就已经不需要内存的用户的了
value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class },
type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector" })
public class UserDetailsServiceAutoConfiguration {
/**
* 密码格式以 {noop} 开头
*/
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
/**
* 检查密码是否是默认格式的表达式
*/
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\{.+}.*$");
private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);
/**
* 为容器中注入一个 {@link InMemoryUserDetailsManager}
* @param properties
* @param passwordEncoder
* @return
*/
@Bean
@ConditionalOnMissingBean(
type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(
User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles)).build());
}
/**
* 返回带有格式的密码
* @param user
* @param encoder
* @return
*/
private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
String password = user.getPassword();
if (user.isPasswordGenerated()) {
logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}
// 是否带有格式
if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
return password;
}
// 加上指定前缀
return NOOP_PASSWORD_PREFIX + password;
}
}
InMemoryUserDetailsManager
- • InMemoryUserDetailsManager是UserDetailsManager的非持久实现,主要用于测试和演示目的,其中不需要完整的持久系统
- • 而UserDetailsManager又是UserDetailsService的实现,这个类才是重点
- • UserDetailsService是加载UserDetails的核心接口,UserDetails是封装用户名,密码等关键信息的
4、SecurityFilterAutoConfiguration
- • 此类作用就是当容器中有一个名称为springSecurityFilterChain的Bean的时候把DelegatingFilterProxyRegistrationBean注册到Spring的容器中去,其实最底层就是将过滤器注册到Servlet容器中,也就是Tomcat容器中,这样在请求进来的时候才能去执行SpringSecurity的过滤器链
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
/**
* 为了将配置好的 springSecurityFilterChain,注册到Tomcat的容器中
* @param securityProperties
* @return
*/
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter().getDispatcherTypes().stream()
.map((type) -> DispatcherType.valueOf(type.name()))
.collect(Collectors.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
}
}