浅析 Spring Security 核心组件

一颗小胡椒2022-07-27 17:46:24

前言

近几天在网上找了一个 Spring Security 和 JWT 的例子来学习,项目地址是

https://github.com/szerhusenBC/jwt-spring-security-demo 

还是有发现的,作为该研究 的一篇文章,通过对Spring SecuritySpring Security 的一个优秀的流程进行分析,并没有参考这篇文章先对 Spring Security 的流程进行分析,并想写一篇官方文档和 Spring Security 的核心组件一些大佬写的Spring Security分析文章,有雷同的地方还请见谅。

Spring Security的核心类

Spring Security 的核心类主要包括以下几个:

  • SecurityContextHolder :存放身份信息的容器
  • Authentication : 身份信息的抽象接口
  • AuthenticationManager : 身份认证器,认证的核心接口
  • UserDetailsS​​ervice:一般用于从数据库中加载身份信息
  • UserDetails : 比较认证,有更详细的身份信息

SecurityContextHolder、Securityontext和Authentication

SecurityContextHolder存储安全信息(安全上下文)的信息,即用于存储身份,认证信息等的包含。SecurityContextHolder默认使用 ThreadLocal策略来存储认证信息,即一种与线程绑定的策略,各个线程执行时都可以获取该线程中的安全时间(安全上下文),影响线程中的线程如果在请求中的安全时间互不影响。

因为身份信息是与当前会话的关系,所以我们可以在程序的任何地方使用获取用户信息的方法,登录用户的姓名的例子如下:

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
     String username = ((UserDetails)principal).getUsername();
} else {
     String username = principal.toString();
}

getAuthentication()返回方法获得了认证信息,准确的说是一个 Authentication实例,AuthenticationSpring Security 中的一个重要接口,直接继承自 Principal 类,应该表示对用户身份信息的抽象,接口来源如下:

public interface Authentication extends Principal, Serializable { 
    //权限信息列表,默认是 GrantedAuthority接口的一些实现
    Collection<? extends GrantedAuthority> getAuthorities(); 
    //密码信息,用户输入的密码字符串,认证后通常会被移除,用于保证安全
    Object getCredentials();
    //细节信息,web应用中通常的接口为 WebAuthenticationDetails,它记录了访问者的ip地址和sessionId的值
    Object getDetails();
    //身份信息,返回UserDetails的实现类
    Object getPrincipal();
    //认证状态,默认为false,认证成功后为 true
    boolean isAuthenticated();
    //上述身份信息是否经过身份认证 
    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

AuthenticationManager、ProviderManager 和 AuthenticationProvider

AuthenticationManager是身份认证器,认证的核心接口,接口源如下:

public interface AuthenticationManager {
  /**
   * Attempts to authenticate the passed {@link Authentication} object, returning a
   * fully populated <code>Authentication</code> object (including granted authorities)
   * @param authentication the authentication request object
   *
   * @return a fully authenticated object including credentials
   *
   * @throws AuthenticationException if authentication fails
   */
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
}

该接口只有一个 authenticate()方法,用于信息,如果认证成功,则返回一个完整的身份信息Authentication,在之前提到的Authentication所有属性都会被填充。

Spring Security 中,一个自己AuthenticationManager默认 对进行请求ProviderManagerProviderManager将派给给自己 AuthenticationProvider的每个人都需要在一个目标列表中 实现目标的AuthenticationProvider跟踪服务提供者。验证结果只有两种情况:抛出一个异常或者完全填充一个 对象的Authentication所有属性。ProviderManager

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
    InitializingBean {
  //维护一个AuthenticationProvider 列表
  private List<AuthenticationProvider> providers = Collections.emptyList();
  private AuthenticationManager parent;
  //构造器,初始化 AuthenticationProvider 列表
  public ProviderManager(List<AuthenticationProvider> providers) {
    this(providers, null);
  }
  public ProviderManager(List<AuthenticationProvider> providers,
      AuthenticationManager parent) {
    Assert.notNull(providers, "providers list cannot be null");
    this.providers = providers;
    this.parent = parent;
    checkState();
  }
  public Authentication authenticate(Authentication authentication)
      throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    boolean debug = logger.isDebugEnabled();
        // AuthenticationProvider 列表中每个Provider依次进行认证
    for (AuthenticationProvider provider : getProviders()) {
      if (!provider.supports(toTest)) {
        continue;
      }
            ...
      try { 
          //调用 AuthenticationProvider 的 authenticate()方法进行认证
        result = provider.authenticate(authentication);
        if (result != null) {
          copyDetails(authentication, result);
          break;
        }
      }
      ...
      catch (AuthenticationException e) {
        lastException = e;
      }
    }
        // 如果 AuthenticationProvider 列表中的Provider都认证失败,且之前有构造一个 AuthenticationManager 实现类,那么利用AuthenticationManager 实现类 继续认证
    if (result == null && parent != null) {
      // Allow the parent to try.
      try {
        result = parent.authenticate(authentication);
      }
            ...
      catch (AuthenticationException e) {
        lastException = e;
      }
    }
        //认证成功
    if (result != null) {
      if (eraseCredentialsAfterAuthentication
          && (result instanceof CredentialsContainer)) {
        // Authentication is complete. Remove credentials and other secret data
        // from authentication
        //成功认证后删除验证信息
        ((CredentialsContainer) result).eraseCredentials();
      }
            //发布登录成功事件
      eventPublisher.publishAuthenticationSuccess(result);
      return result;
    }
    // 没有认证成功,抛出一个异常
    if (lastException == null) {
      lastException = new ProviderNotFoundException(messages.getMessage(
          "ProviderManager.providerNotFound",
          new Object[] { toTest.getName() },
          "No AuthenticationProvider found for {0}"));
    }
    prepareException(lastException, authentication);
    throw lastException;
  }

ProviderManager中的 authenticationManager成功成功失败成功尝试,认证即返回失败,如果所有的Provider都认证,认证失败返回 ProviderManager无效 ProviderNotFoundException

是一个接口,接口定义如下:AuthenticationProvider

public interface AuthenticationProvider {
    //认证方法
  Authentication authenticate(Authentication authentication)
      throws AuthenticationException;
    //该Provider是否支持对应的Authentication
  boolean supports(Class<?> authentication);
}

在 ProviderManager的 Javadoc 曾提到,

如果多个 AuthenticationProvider 支持传递的 Authentication 对象,则第一个能够成功验证 Authentication 对象的人会确定结果,并覆盖之前支持的 AuthenticationProvider 抛出的任何可能的 AuthenticationException 。成功认证后,不会尝试后续的 AuthenticationProvider 。如果任何支持 AuthenticationProvider 的身份验证未成功,则最后抛出的 AuthenticationException 将被重新抛出

大致意思是:

如果有多个AuthenticationProvider都支持同一个Authentication对象,那么第一个能够成功验证的Provder将填充 其返回结果,那么早期支持的AuthenticationProvider抛出可能的AuthenticationException。成功验证后,将如果所有验证都不会在中验证)的AuthenticationProvider。如果 AuthenticationProvider没有成功验证,则将抛出最后的ProviderProvider抛出异常。(AuthenticationProvider可以配置配置类的配置)

PS

不同的 AuthenticationProvider认证支持不同的不同的对象,分别对应不同的 Authentication对象,那么当一个 不同的对象AuthenticationProvier进入 ProviderManager的内部时, 它们会在AuthenticationProvider它们中挑选其对的提供者相应的对象进行验证。

不同的认证逻辑是不同的,即 使用用户名和密码,使用用户名和密码,登录提供了简单的实现 ,这也是 一个用户在登录时使用的方式AuthenticationProvider,如果用户名和密码不一样,那么 它也是一个用户可以登录的方式。密码和 我们一般要在接口中,并把 Spring Security 配置类实现其配置,这样也用于在使用中进行详细的认证,然后该接口返回一个,它包含了身份信息,比如从数据库拿取的信息密码和AuthenticationProvider的认证核心,即加载的 来列表用户的密码是否匹配,用户详情和验证以及其他的密码(关于 和就是使用的介绍在下面介绍。)。,比如QQ登录,那么就需要设置的 ,这里就不细说了。AuthenticationProviderDaoAuthenticationProviderUserDetailsServiceGrantedAuthorityUserDetailsServiceDaoAuthenticationProviderUserDetailsUserDetailsUserDetailsServiceUserDetailsAuthenticationProvider

认证成功后清除验证信息

在 ProviderManager 的源中我还发现一点,在认证成功后清除验证信息,如下:

if (eraseCredentialsAfterAuthentication
    && (result instanceof CredentialsContainer)) {
  // Authentication is complete. Remove credentials and other secret data
  // from authentication
  //成功认证后删除验证信息
  ((CredentialsContainer) result).eraseCredentials();
}

从安全 3.1,在请求认证成功后 ProviderManager删除 Authenticationspring 中的认证信息,准确地说,一般删除是密码信息,这保证跟密码的安全。我可以直接执行删除操作的步骤如下:

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    public void eraseCredentials() {
        super.eraseCredentials();
        //使密码为null
        this.credentials = null;
    }
}
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
...
public void eraseCredentials() {
    //擦除密码
    this.eraseSecret(this.getCredentials());
    this.eraseSecret(this.getPrincipal());
    this.eraseSecret(this.details);
}
private void eraseSecret(Object secret) {
    if (secret instanceof CredentialsContainer) {
        ((CredentialsContainer)secret).eraseCredentials();
    }
 }
}

从就可以很舒服地使用私人密码。

UserDetailsS​​ervice 和 UserDetails

UserDetailsService简单说就是加载UserDetails的接口(一般从数据库),而UserDetails包含了更详细的用户信息,定义如下:

public interface UserDetails extends Serializable {
   Collection<? extends GrantedAuthority> getAuthorities();
   String getPassword();
   String getUsername();
   boolean isAccountNonExpired();
   boolean isAccountNonLocked();
   boolean isCredentialsNonExpired();
   boolean isEnabled();
}

UserDetails 和 Authentication 接口类似,它们有用户名、权限。它们的区别如下:

  • Authentication的getCredentials()一般与Users中的getPassword()不一样,是用户提交的密码,后面是用户正确的从数据库加载的密码),AuthenticationProvider分别对中进行对比。
  • Authentication 中的 getAuthorities() 形成是由 UserDetails 的 getAuthorities() 传递的。
  • Authentication 中的 getUserDetails() 中的 UserDetails 用户详细信息经过 AuthenticationProvider认证后填充的。

认证样本示例

下面来看一个官方文档提供的例子,代码如下:

public class SpringSecuriryTestDemo {
    private static AuthenticationManager am = new SampleAuthenticationManager();
    public static void main(String[] args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.println("Please enter your username:");
            String name = in.readLine();
            System.out.println("Please enter your password:");
            String password = in.readLine();
            try {
                Authentication request = new UsernamePasswordAuthenticationToken(name, password);
                Authentication result = am.authenticate(request);
                SecurityContextHolder.getContext().setAuthentication(request);
                break;
            } catch (AuthenticationException e) {
                System.out.println("Authentication failed: " + e.getMessage());
            }
        }
        System.out.println("Successfully authenticated. Security context contains: " + SecurityContextHolder.getContext().getAuthentication());
    }
    static class SampleAuthenticationManager implements AuthenticationManager {
        static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();
        static {
            AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
        }
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            if (authentication.getName().equals(authentication.getCredentials())) {
                return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES);
            }
            throw new BadCredentialsException("Bad Credentials");
        }
    }
}

测试如下:

Please enter your username:
pjmike
Please enter your password:
123
Authentication failed: Bad Credentials
Please enter your username:
pjmike
Please enter your password:
pjmike
Successfully authenticated. 
Security context contains: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@441d0230:
Principal: pjmike; 
Credentials: [PROTECTED];
Authenticated: true; Details: null; 
Granted Authorities: ROLE_USER

上面的例子很不是源头,只是为了证明的Demo,而且很简单的验证过程,但麻雀虽小,五脏俱全,包括Spring Security的核心组件,思想了Spring Security认证的基本原理解读一下:

  • 用户名和密码被封装到 UsernamePasswordAuthentication的实例中(该类是 Authentication接口的实现)
  • 该 Authentication递交给 AuthenticationManager进行身份验证
  • 认证成功后,AuthenticationManager返回一个完整的 Authentication身份实例,该实例包含权限信息、信息、细节信息,但密码会被删除
  • 通过调用 的对象SecurityContextHolder.getContext().setAuthentication(…)返回的信息 Authentication

通过上面一个简单的例子,我们大致了解了 Spring Security 的基本思想,但是要理清 Spring Security 的认证流程这件事,我们还需要深入了解 Spring Security 的认证流程,深入了解 Spring Security 的认证流程。

小结

这篇文章主要介绍了这篇文档后分析了一些核心组件,参考了官方相关的一些核心组件,对有一个基本组件,Spring 核心才能解释 Spring Security 的一些详细的分析认证过程。

用户接口
本作品采用《CC 协议》,转载必须注明作者和本文链接
目前,多数项目会有多数据源的要求,或者是主从部署的要求,所以我们还需要引入mybatis-plus关于多数据源的依赖:。#设置默认的数据源或者数据源组,默认值即为master. true未匹配到指定数据源时抛异常,false使用默认数据源。表名注解,用于标识实体类对应的表。其说明如下,关于这些书写,常规情况基本很少用到,不做多余解释了:@Documented
OWT渗透WiFi网络
2021-10-20 21:51:30
一款功能强大的攻击性WiFi渗透测试套件
SharpStrike是一款基于C#开发的后渗透工具,该工具可以使用CIM或WMI来查询远程系统。除此之外,该工具还可以使用研究人员提供的凭证信息或使用当前的用户会话。 注意:SharpStrike中的某些命令将使用PowerShell结合WMI以实现其功能。
关于CloudPulseCloudPulse是一款针对AWS云环境的SSL证书搜索与分析引擎,广大研究人员可以使用该工具简化并增强针对SSL证书数据的检索和分析过程。在网络侦查阶段,我们往往需要收集与目标相关的信息,并为目标创建一个专用文档,以辅助我们识别目标组织可用的渗透路径。CloudPulse能够从Trickest Cloud中的AWS EC2主机中获取大量的SSL证书,并以此来简化我们针对
一次简单的渗透测试记录
0x01 目标某平台系统0x02 流程0x03 测试拿到站点后先做信息收集,扫描目录看看有无敏感信息寥寥无几,没有任何信息,启动burpsuite打开网站走一遍流程。在创建目标处存在图片上传接口,上传shell试试。
Pentaho Business Analytics CVE-2021-31599远程命令执行等系列漏洞分析。
据外媒,全球知名航空通讯公司SITA表示其航空客运系统疑似遭遇数据泄露事件,该公司在2月24日的一份声明中表示,存储在其美国服务器上的某些乘客数据已被泄露。并联系了受影响的航空公司。
​如何看待阿里云等大厂平台相继发生崩溃故障?
一颗小胡椒
暂无描述