『Spring Security』(二) 重要概念及部分流程

认证相关重要概念

SecurityContextHolder

Spring Security身份验证模型的核心是SecurityContextHolder。它包含SecurityContext

image-20201107103525915

Spring Security 的 SecurityContextHolder ,用于存储通过身份验证的人员的详细信息。

SecurityContext

SecurityContextHolder中可获取 SecurityContextSecurityContext中包含一个身份验证对象。

Authentication

身份验证在Spring Security中有两个主要用途:

  • AuthenticationManager的输入,用于提供用户的身份验证的凭据。在这种情况下使用时,isAuthenticated()返回false。
  • 代表当前经过身份验证的用户。可以从SecurityContext获得当前的身份验证。

其中包含三块内容:

  • principal

    用户的抽象。使用用户名/密码进行身份验证时,通常是UserDetails的实例。

  • credentials

    通常是密码。在许多情况下,将在验证用户身份后清除此内容,以确保它不会泄漏。

  • authorities

    GrantedAuthoritys是授予用户的权限。通常是角色。

GrantedAuthority

GrantedAuthoritys是授予用户的权限。通常是角色。

Spring Security 的过滤器,要能够识别这些权限可以访问哪些资源,并在权限不足时抛出异常。

使用基于用户名/密码的身份验证时,GrantedAuthoritys通常由UserDetailsService加载。

AuthenticationManager

AuthenticationManager是用于定义 Spring Security 的过滤器如何执行身份验证的API。验证过后,由调用AuthenticationManager 的控制器(即Spring SecurityFilters)在SecurityContextHolder上设置返回的Authentication

最常见的实现是ProviderManager。

ProviderManager

ProviderManager 委托给 authenticationprovider 列表进行身份验证。
每个AuthenticationProvider可以指出身份验证应该是成功的、失败的,或者指出它不能做出决定,并允许下游的AuthenticationProvider做出决定。

image-20201107141228817

每个AuthenticationProvider可以执行特定类型的身份验证。例如,一个AuthenticationProvider可能能够验证用户名/密码,而另一个可能能够验证SAML断言。

AuthenticationProvider

可以将多个AuthenticationProviders注入ProviderManager。每个AuthenticationProvider执行特定的身份验证类型。例如,DaoAuthenticationProvider支持基于用户名/密码的身份验证,而JwtAuthenticationProvider支持对JWT令牌的身份验证。

AuthenticationEntryPoint

AuthenticationEntryPoint用于发送HTTP响应,以从客户端请求凭据。就是当用户未经认证,访问系统路径时,AuthenticationEntryPoint实现可能会执行重定向到登录页面,使用WWW-Authenticate标头进行响应等等操作。

AbstractAuthenticationProcessingFilter

AbstractAuthenticationProcessingFilter被当作基础过滤器,用于验证用户的凭据,可以对提交给它的任何身份验证请求进行身份验证。

image-20201109203557488

  1. 当用户提交认证凭据时,AbstractAuthenticationProcessingFilter将从要验证的HttpServletRequest创建一个Authentication。创建的身份验证类型取决于AbstractAuthenticationProcessingFilter的子类。

    例如,UsernamePasswordAuthenticationFilter根据在HttpServletRequest中提交的用户名和密码创建UsernamePasswordAuthenticationToken

  2. Authentication传递到AuthenticationManager进行身份验证。

  3. 如果验证失败,则流程结束,登陆失败。

    • 清除 SecurityContextHolder
    • 调用RememberMeServices.loginFail。如果remember me没有配置,则不会调用。
    • 调用AuthenticationFailureHandler
  4. 如果验证成功,则表明登陆成功

    • 把登录通知到 SessionAuthenticationStrategy
    • Authentication 设置到SecurityContextHolder上。
    • 调用RememberMeServices.loginSuccess。如果remember me没有配置,则不会调用。
    • ApplicationEventPublisher发布一个InteractiveAuthenticationSuccessEvent
    • 调用AuthenticationSuccessHandler

BCryptPasswordEncoder

BCryptPasswordEncoder实现使用广泛支持的bcrypt算法对密码进行哈希处理。一般我们会用该类,对密码进行加密。

Username/Password Authentication

验证用户身份的最常见方法之一是验证用户名和密码。因此,Spring Security为使用用户名和密码进行身份验证提供了全面的支持。

HttpServletRequest 获取用户名、密码,自带以下机制:

  • Form Login
  • Basic Authentication
  • Digest Authentication

储存机制,提供以下几种:

  • 基于内存的简单存储 (In-Memory Authentication)
  • 关系型数据库存储 (JDBC Authentication)
  • 自定义数据存储 (UserDetailsService)

基于内存的简单存储

Spring Security 默认密码随机生成,并打印到控制台。也就是上面说的,基于内存的简单存储。

2020-11-09 19:18:39.634  INFO 17384 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: 59423ac9-f9a0-41b5-a4c9-f508102217c4
复制代码

我们打开 UserDetailsServiceAutoConfiguration 类,主要方法如下

@Bean
@ConditionalOnMissingBean(
            type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@Lazy
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
            ObjectProvider<PasswordEncoder> passwordEncoder) {
    // 从配置文件中获取 user
        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());
    }
 
    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;
    }
复制代码

我们暂且不过深看源码,只需要指导,该类向 Spring 注入了一个 InMemoryUserDetailsManager内存用户管理器,并且创建了一个用户。由此实现了,用户名/密码登陆。

Spring SecurityInMemoryUserDetailsManager实现了UserDetailsService,以支持对在内存中基于用户名/密码的身份验证。

在配置文件中,配置用户名密码也是如此。

自定义存储登陆流程

用户名/密码登陆,Spring Security 也提供了几种用户存储方式:内存储存、通过 JDBC 连接关系数据库、自定义储存等。最常用的就是自定义储存方式,其实就是存到数据库中,自己存,自己取。

官网上的图,可以让我们很清晰的了解到自定义存储的认证过程。

image-20201106231908803

  1. 通过读取用户名和密码的身份验证过滤器将 UsernamePasswordAuthenticationToken 传递给 AuthenticationManager,该身份管理器由 ProviderManager 实现。
  2. ProviderManager 中使用AuthenticationProvider 的实现类 DaoAuthenticationProvider
  3. DaoAuthenticationProviderUserDetailsService 调用方法获取 UserDetails
  4. DaoAuthenticationProvider 使用PasswordEncoder验证上一步返回的UserDetails上的密码。
  5. 身份验证成功后,返回的身份验证的类型为UsernamePasswordAuthenticationToken,其主体为我们配置的UserDetailsService返回的UserDetails。最终,返回的UsernamePasswordAuthenticationToken将由身份验证过滤器在SecurityContextHolder上设置。

所以一般自定义储存登陆,都要扩展UserDetailServiceUserDetails

授权相关重要概念

Authentication 中包含 GrantedAuthority,表示该主体的权限。

GrantedAuthority对象由AuthenticationManager插入Authentication对象,并在以后做出授权决策时由AccessDecisionManager读取。

public interface GrantedAuthority extends Serializable {
    String getAuthority();
}
复制代码

Spring Security包含一个的GrantedAuthority实现,即SimpleGrantedAuthority,构造方法接收一个String。在 Spring Security 体系中,一般都使用SimpleGrantedAuthority

AccessDecisionManager

Spring Security提供了拦截器,用于控制访问,例如拦截http请求。

AccessDecisionManagerAbstractSecurityInterceptor调用,并决策该请求是否被允许。

AccessDecisionManager接口包含三种方法:

// 决策该请求是否被允许
void decide(Authentication authentication, Object secureObject,
    Collection<ConfigAttribute> attrs) throws AccessDeniedException;
// 在开始时,AbstractSecurityInterceptor将调用support(ConfigAttribute)方法,以确定AccessDecisionManager是否可以处理传递的ConfigAttribute。
boolean supports(ConfigAttribute attribute);
 
// 安全拦截器实现调用support(Class)方法,以确保配置的AccessDecisionManager支持安全拦截器将显示的安全对象的类型。
boolean supports(Class clazz);
复制代码

FilterSecurityInterceptor

FilterSecurityInterceptorHttpServletRequests提供授权。它是Security过滤器之一。

image-20201109213357957

  1. FilterSecurityInterceptorSecurityContextHolder获得Authentication
  2. FilterSecurityInterceptor根据传递到FilterSecurityInterceptor中的HttpServletRequestHttpServletResponseFilterChain创建一个FilterInvocation
  3. FilterInvocation传递给SecurityMetadataSource以获取ConfigAttributes。
  4. AuthenticationFilterInvocationConfigAttributes传递给AccessDecisionManager
  5. 如果授权被拒绝,则抛出AccessDeniedExceptionExceptionTranslationFilter处理AccessDeniedException
  6. 如果授予访问权限,FilterSecurityInterceptor继续使用FilterChain,允许应用程序正常处理。

默认情况下,Spring Security的授权将要求对所有请求进行身份验证。

FilterInvocationSecurityMetadataSource

上面说到,要使用SecurityMetadataSource获取ConfigAttributes。

FilterInvocationSecurityMetadataSource 继承 SecurityMetadataSource,而且没有扩展方法。在扩展时,一般实现此方法,实现角色与url匹配。

public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource {
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享