• Spring Security 中重要对象汇总


    前言

    已经写了好几篇关于 Spring Security 的文章了,相信很多读者还是对 Spring Security 的云里雾里的。这是因为对 Spring Security 中的对象还不了解。本文就来介绍介绍一下常用对象。

    认证流程

    SecurityContextHolder

    用户认证通过后,为了避免用户的每次操作都进行认证,可将用户的信息保存在会话中。Spring Security 提供会话管理,认证通过后将身份信息放入 SecurityContextHolder 上下文,SecurityContext 与当前线程进行绑定,方便获取用户身份。

    1. // 获取当前登录的用户信息
    2. Authentication authentication =
    3. SecurityContextHolder.getContext().getAuthentication();
    4. 复制代码

    AuthenticationManager

    认证管理器,AuthenticationManager 是认证相关的核心接口,是发起认证的入口,用于处理认证请求。接口只提供了一个认证方法,方法接收一个未通过认证 Authentication 对象,返回一个通过认证的 Authentication 对象。最常见的实现是ProviderManager

    1. public interface AuthenticationManager {
    2. Authentication authenticate(Authentication var1) throws AuthenticationException;
    3. }
    4. 复制代码

    ProviderManager

    提供商管理器,ProviderManagerAuthenticationManager 的一个实现类,提供了基本的认证逻辑和方法。它其中包含了一个 List 的 AuthenticationProvider 的属性,该属性存放多种认证方式!为什么需要这个属性呢?当Spring Security默认提供的认证方式不能满足需求时,就可以通过 AuthenticationProvider 接口来扩展出其他认证方式,比如邮箱+验证码,手机号码+验证码登录。

    AuthenticationProvider

    AuthenticationProvider(身份验证提供者),可以将多个AuthenticationProvider实例添加到ProviderManager中。其每个AuthenticationProvider可以执行特定的 Authentication (身份验证)类型。例如:DaoAuthenticationProvider支持基于用户名+密码的 UsernamePasswordAuthenticationToken 身份验证。也可以自定义认证方式,比如自定义EmailVerificationCodeAuthenticationProvider支持邮箱 + 验证码的 EmailVerificationCodeAuthenticationToken 身份验证。

    1. public interface AuthenticationProvider {
    2. Authentication authenticate(Authentication authentication) throws AuthenticationException;
    3. boolean supports(Class authentication);
    4. }
    5. 复制代码

    该接口中有两个方法,如下:

    • authenticate() 方法接收一个未通过认证 Authentication 对象,返回一个通过认证的 Authentication 对象。可以实现 authenticate() 方法来自定义身份验证逻辑。

    • supports(Class authentication) 方法接收一个 Authentication(身份验证) 对象,如果 AuthenticationProvider 支持指定的身份验证对象,则返回 true。 但是返回 true 并不保证 AuthenticationProvider 能够对提供的 Authentization 类实例进行身份验证。它只是表明它可以支持对其进行更深入的验证。AuthenticationProvider 仍可以从 authenticate() 方法返回 null,以尝试其他的 AuthentitationProvider 进行验证。

    Authentication

    Authentication(身份验证) 接口是 Spring Security 中身份验证流程的顶级接口,该接口定义了如下方法:

    1. public interface Authentication extends Principal, Serializable {
    2. Collectionextends GrantedAuthority> getAuthorities();
    3. Object getCredentials();
    4. Object getDetails();
    5. Object getPrincipal();
    6. boolean isAuthenticated();
    7. void setAuthenticated(boolean var1) throws IllegalArgumentException;
    8. }
    9. 复制代码

    方法含义如下:

    方法描述
    getAuthorities获取登录用户的权限列表
    getCredentials获取凭据。用户密码登录,这个字段就是密码信息,在认证过后通常会被移除,用于保障安全。如果是手机号验证码登录,那这个字段存的就是验证码
    getDetails包含了一些认证时的信息,默认的实现为 WebAuthenticationDetails,记录了访问者的远程地址和sessionId的值。
    getPrincipal身份信息,默认情况下返回的是 UserDetails的实例
    isAuthenticated是否通过认证,通过认证为 true
    setAuthenticated设置是否已认证
    getName用户名

    具体响应内容可以参考如下:

    1. {
    2. "authorities": [
    3. {
    4. "authority": "ROLE_admin"
    5. },
    6. {
    7. "authority": "ROLE_user"
    8. }
    9. ],
    10. "details": {
    11. "remoteAddress": "0:0:0:0:0:0:0:1",
    12. "sessionId": "D77AF630A476DEE7A2A75B1D751C4CF1"
    13. },
    14. "authenticated": true,
    15. "principal": {
    16. "password": null,
    17. "username": "cxyxj",
    18. "authorities": [
    19. {
    20. "authority": "ROLE_admin"
    21. },
    22. {
    23. "authority": "ROLE_user"
    24. }
    25. ],
    26. "accountNonExpired": true,
    27. "accountNonLocked": true,
    28. "credentialsNonExpired": true,
    29. "enabled": true
    30. },
    31. "credentials": null,
    32. "name": "cxyxj"
    33. }
    34. 复制代码

    Authentication 本身是一个接口,它有很多实现类:

    在众多的实现类中,我们最常用的就是 UsernamePasswordAuthenticationToken(用户名密码身份验证令牌),但是这个类就只有简单的50行左右的代码,其中有两个属性,principal 代表用户名,credentials 代表密码。还有两个构造方法,一个是代表未认证的,一个是代表已认证的;三个set、get方法,一个擦除凭据方法。该类继承了 AbstractAuthenticationToken,其大部分逻辑在父类中,当然父类的逻辑也非常简单。 所以 UsernamePasswordAuthenticationToken(用户名密码身份验证令牌)的作用就是将用户输入的用户名和密码进行封装,并供给 AuthenticationManager 进行验证。

    UserDetails

    这个接口定义了用户的核心信息,比如用户名、密码、账号是否过期、是否锁定等!默认实现类org.springframework.security.core.userdetails.User。在 Spring Security 中,如果自定义认证逻辑时,需要实现该接口进行扩展,来保存自己系统的用户信息。接口定义如下方法:

    方法描述
    getAuthorities获取用户权限
    getPassword获取用户密码
    getUsername获取用户名
    isAccountNonExpired账户是否未过期,true:未过期,false:过期
    isAccountNonLocked账户是否未锁定,true:未锁定,false:锁定
    isCredentialsNonExpired凭证(密码)是否未过期,true:未过期,false:过期
    isEnabled账户是否启用,true:启用,false:禁用

    一个正常能登录的账号,四个状态都是为 true 的。

    UserDetailsService

    在 Spring Security 中,什么也不进行配置时,账号和密码是由 Spring Security 自动生成的。 但在实际的项目中账号、密码是从数据库中查询出来的。所以我们需要自定义认证逻辑。 此时需要实现 UserDetailsService 接口。而 UserDetailsService 接口中只定义了一个方法,作用是根据用户名加载用户,获得 UserDetails 对象。

    1. public interface UserDetailsService {
    2. // 按用户名加载用户
    3. UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    4. }
    5. 复制代码

    可以参考自定义逻辑如下:

    1. @Override
    2. public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
    3. LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.lambdaQuery();
    4. queryWrapper.eq(SysUser::getAccount, account);
    5. // 根据用户名查询用户
    6. SysUser sysUsers = sysUserMapper.selectOne(queryWrapper);
    7. if (Objects.isNull(sysUsers)) {
    8. Assert.isTrue(true,"用户名或者密码错误");
    9. }
    10. // 获得用户角色信息
    11. List<String> roles = sysUserMapper.selectByUserId(sysUsers.getUserId());
    12. // 构建 SimpleGrantedAuthority 对象
    13. List<SimpleGrantedAuthority> authorities = roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    14. return new SysUserDetails(sysUsers, authorities);
    15. }
    16. 复制代码

    除了需要手动实现 UserDetailsService 接口的方式外,Spring Security 也内置了几种方式。 我们来看下 UserDetailsService 都有哪些实现类:

    • InMemoryUserDetailsManager:内存用户,这种方式在学习 Spring Security 的时候,用的非常多。

    • JdbcUserDetailsManager:通过 JDBC 的方式将数据库和 Spring Security 连接起来。它自己提供了一个数据库脚本,脚本路径如下:org/springframework/security/core/userdetails/jdbc/users.ddl。脚本的内容呢,应该是不符合项目实际开发的,所以这个也是在学习的时候可以使用使用。

    PasswordEncoder

    PasswordEncoder 接口用于执行密码的单向转换,以便安全地存储密码。

    InteractiveAuthenticationSuccessEvent

    身份验证成功后,发布一个名为InteractiveAuthenticationSuccessEvent的事件通知给到应用上下文,用于告知身份验证已经成功。

    FilterChainProxy

    在 Spring Security 的默认配置中,将创建一个名为 springSecurityFilterChain 的 servlet 过滤器作为bean。默认情况下,Spring Security 内置了一个过滤链,链中有 15 个过滤器。

    想要了解更多请前往: 深入理解 FilterChainProxy【源码篇】

    ExceptionTranslationFilter

    ExceptionTranslationFilter 异常转换过滤器位于整个 springSecurityFilterChain 的后方,用来转换整个链路中出现的异常。此过滤器本身不处理异常,而是将认证过程中出现的异常交给内部维护的一些类去处理,一般处理两大类异常:AccessDeniedException 已登录无权限访问异常和 AuthenticationException 未认证访问异常。

    HeaderWriterFilter

    用来给http响应添加一些Header,比如X-Frame-Options, X-XSS- Protection*,X-Content-Type-Options.

    CsrfFilter

    用于防止csrf攻击(跨站点请求伪造(Cross- site request forgery))。

    LogoutFilter

    处理注销的过滤器。

    RequestCacheAwareFilter

    内部维护了一个RequestCache,用于缓存request请求。

    SecurityContextHolderAwareRequestFilter

    对ServletRequest进行了一次包装,使得request 具有更加丰富的API。

    SessionManagementFilter

    和session相关的过滤器,内部维护了一个 SessionAuthenticationStrategy,两者组合使用,常用来防止会话固定攻击保护( session- fixation protection attack ),以及限制同一用户开启多个会话的数量。

    AnonymousAuthenticationFilter

    匿名身份过滤器,spring security为了兼容未登录的访问,也走了一套认证流程,只不过是一个匿名的身份。

    UsernamePasswordAuthenticationFilter

    表单提交了username和password参数,被封装成token进行一系列的认证,便是主要通过这个过滤器完成的。在表单认证的流程中,这是最最关键的过滤器。

    AbstractAuthenticationProcessingFilter

    翻译为:抽象身份验证处理过滤器,这是一个抽象类,定义了认证处理的过程。是一个模板类。默认的实现为 UsernamePasswordAuthenticationFilter,根据用户名、密码进行身份验证,如果需要自定义身份验证,比如手机验证码登录,就需要继承该类。

    授权

    当用户访问 Spring Security 中一个受保护的资源时,需要使用投票器和表决机制,投票器根据用户的角色投出赞成或者反对票,表决方式则根据投票器的结果进行表决。

    AccessDecisionManager

    访问决策管理器,AccessDecisionManagerAbstractSecurityInterceptor调用。AccessDecisionManager 采用投票的方式来确定是否能够访问受保护资源。 AccessDecisionManager 中包含的多个 AccessDecisionVoter,Voter 将会被用来对Authentication是否有权访问受保护对象进行投票, AccessDecisionManager 根据投票结果,做出最终决策。

    AccessDecisionManager 访问决策管理器还有三个子类决策器,分别是:

    • AffirmativeBased:存在多个投票器时,有一个投票器同意,则请求就允许访问,也就是一票通过;默认使用的决策器。
    • UnanimousBased:存在多个投票器时,如果有投票器拒绝,则请求不允许访问,也就是一票否决。
    • ConsensusBased:存在多个投票器时,大多数投票器同意。则请求就允许访问,也就是少数服从多数。如果是平局,则看 allowIfEqualGrantedDeniedDecisions 的值来判断是否通过,在默认情况下allowIfEqualGrantedDeniedDecisions值是true,也就是说平票的情况下,请求允许访问。

    注意:不论是哪个决策器,如果所有投票器全部弃权则表示通过。

    AccessDecisionVoter

    翻译为:投票机制/投票器。在 Spring Security 中,投票机制是由 AccessDecisionVoter 接口来定义的,它有许多的实现:

    实现有好多种,我们可以选择其中一种或多种投票机制,也可以自定义投票机制,默认的投票机制是 WebExpressionVoter。

    1. public interface AccessDecisionVoter<S> {
    2. int ACCESS_GRANTED = 1;
    3. int ACCESS_ABSTAIN = 0;
    4. int ACCESS_DENIED = -1;
    5. boolean supports(ConfigAttribute attribute);
    6. boolean supports(Class<?> clazz);
    7. int vote(Authentication authentication, S object,
    8. Collection<ConfigAttribute> attributes);
    9. }
    10. 复制代码
    • 三个常量含义分别为:1 表示同意;0 表示弃权;-1 表示拒绝。
    • 两个 supports 方法用来判断投票器是否支持当前请求。
      1. vote 则是具体的投票方法;在不同的实现类中实现。authentication 表示当前登录主体;object 表示正在调用的受保护接口;attributes 表示当前所访问的接口所需要的角色集合。

    • RoleVoter:判断当前请求是否具备该接口所需要的角色。
    • RoleHierarchyVoter 是 RoleVoter 的一个子类,在 RoleVoter 角色判断的基础上,引入了角色分层管理,也就是角色继承。
    • WebExpressionVoter:基于表达式权限控制
    • Jsr250Voter:处理 Jsr-250 权限注解的投票器,如 @PermitAll@DenyAll 等
    • PreInvocationAuthorizationAdviceVoter:使用 @PreFilter 和 @PreAuthorize 注解处理的权限,通过 PreInvocationAuthorizationAdvice 来授权。

    AbstractSecurityInterceptor

    根据注释翻译了一下:为安全对象实现安全拦截的抽象类,干的事情说白点就是对未放行的资源,根据用户的权限来控制是否能访问的拦截器。由于这是一个抽象类,所以只定义了一些逻辑方法,具体执行都是子类去调用的。

    FilterSecurityInterceptor

    从 FilterChainProxy 章节中的截图来看,FilterSecurityInterceptor 位于 Spring Security Filter Chain 中的最后一个 Filter。这是一个过滤器,它会拦截HTTP请求,进行鉴权处理。

    MethodSecurityInterceptor

    它还实现了 MethodInterceptor,所以这是一个方法拦截器,基于 Spring AOP 实现了方法拦截,对方法进行鉴权处理。比如在方法上标注了@RolesAllowed@PermitAll@PreAuthorize等等注解。

    AspectJMethodSecurityInterceptor

    继承了 MethodSecurityInterceptor,基于 Aspectj 实现方法拦截。

    区别

    • 一个是过滤器,一个是方法拦截器,所以他们拦截对象是不同,一个是拦截Http请求,一个是拦截方法。
    • 两者都调用了父类的beforeInvocation方法,但是传入的参数是不一样的,FilterSecurityInterceptor传入的是FilterInvocation,MethodInterceptor传入的是MethodInvocation。
    • 两者维护的 SecurityMetadataSource 不一样,MethodSecurityInterceptor 中维护的是MethodSecurityMetadataSource,FilterSecurityInterceptor维护的是 FilterInvocationSecurityMetadataSource。

    SecurityMetadataSource

    获取授权配置的接口。可以自定义实现该接口,比如从数据库中加载ConfigAttribute。在 Spring Security中,给该接口提供了两个子类,继承图如下:

    • MethodSecurityMetadataSource:由Spring Security Core定义,用于表示安全对象是方法调用(MethodInvocation)的安全元数据源;存放的是所有类和方法,会根据当前执行的类和方法,去内存中遍历,查询到当前执行方法配置的权限注解,然后对其进行授权判断。

    • FilterInvocationSecurityMetadataSource:由Spring Security Web定义,用于表示安全对象是Web请求(FilterInvocation)的安全元数据源;存放的是 HttpSecurity 配置类中配置的授权规则。

    配置如下:

    1. http.authorizeRequests()
    2. // 如果用户具备 admin 权限,就允许访问。
    3. .antMatchers("/cxyxj/**").hasAuthority("ROLE_admin")
    4. // 如果用户具备给定权限中某一个,就允许访问。
    5. .antMatchers("/admin/demo").hasAnyAuthority("ROLE_admin", "ROLE_System")
    6. // 如果用户具备 user 权限,就允许访问。注意不需要手动写 ROLE_ 前缀,写了会报错
    7. .antMatchers("/security/**").hasRole("user")
    8. //如果请求是指定的 IP 就允许访问。
    9. .antMatchers("/admin/demo").hasIpAddress("192.168.64.5")
    10. .anyRequest() //其他请求
    11. .authenticated(); //需要认证才能访问
    12. 复制代码

    参考文献

  • 相关阅读:
    隐式微分方程求解Matlab
    java计算机毕业设计竞赛信息发布及组队系统源码+数据库+系统+lw文档+mybatis+运行部署
    首次扭亏为盈后,货拉拉还想靠造车更上一层楼?
    【无代码】【VR开发】【Unity】【VRTK】4-导入VRTK Tilia Package
    简单的博客系统(CSS+HTML)
    互联网系统安全(一)
    五、K8S之Service
    【Vue 路由(vue—router)二】路由传参(params的类型 、Query参数的类型、路由name)
    国内外9大最佳测试管理平台
    详解容灾恢复过程中跨数据中心级的关键故障切换
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/128109927