Spring Security详解(一)认证之核心组件和服务

什么是Spring Security验证?

让我们考虑一个大家都很熟悉的标准的验证场景。

  1. 提示用户输入用户名和密码进行登录。
  2. 该系统 (成功) 验证该用户名的密码正确。
  3. 获取该用户的环境信息 (他们的角色列表等).
  4. 为用户建立安全的环境。
  5. 用户进行,可能执行一些操作,这是潜在的保护的访问控制机制,检查所需权限,对当前的安全的环境信息的操作。
    而对于Spring Security来说,假设是用户名密码登陆,那执行顺序就是:
  1. 用户通过url:/login 登录,该过滤器接收表单用户名密码
  2. 判断用户名密码是否为空
  3. 生成 UsernamePasswordAuthenticationToken
  4. 将 Authentiction 传给 AuthenticationManager#authenticate 方法进行认证处理
  5. AuthenticationManager 默认是实现类为 ProviderManager ,ProviderManager 委托给 AuthenticationProvider 进行处理
  6. UsernamePasswordAuthenticationFilter 继承了 AbstractAuthenticationProcessingFilter 抽象类,AbstractAuthenticationProcessingFiltersuccessfulAuthentication 方法中对登录成功进行了处理,通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext
  7. 下次请求时,在过滤器链头的 SecurityContextPersistenceFilter 会从 Session 中取出用户信息并生成 Authentication(默认为 UsernamePasswordAuthenticationToken),并通过 SecurityContextHolder.getContext().setAuthentication() 方法将 Authentication 认证信息对象绑定到 SecurityContext
  8. 需要权限才能访问的请求会从 SecurityContext 中获取用户的权限进行验证

下面来逐个分析一下每个类。

1.核心组件

这一节主要介绍一些Spring Security中核心的java类,他们之间的依赖,构建起了整个框架。

1.1 SecurityContextHolder

SecurityContextHolder 是最基本的对象,它负责存储当前安全上下文信息。即保存着当前用户是什么,是否已经通过认证,拥有哪些权限。。。等等。SecurityContextHolder默认使用**ThreadLocal**策略来存储认证信息,意味着这是一种与线程绑定的策略。在Web场景下的使用Spring Security,在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。而在非Web场景下,比如Swing环境下,Spring提供在启动时使用策略配置SecurityContextHolder。这部分具体的配置请自行翻阅官方文档。我就不在此赘述了,之后的讲解也将基于Web场景。

获取有关当前用户的信息
Spring Security使用一个Authentication对象来表示这些信息。通常不需要自己创建一个Authentication对象,但可以在程序的任何一个地方获取到用户信息,下面时官方提供的获取用户信息:


Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

if (principal instanceof UserDetails) {
	String username = ((UserDetails)principal).getUsername();
} else {
	String username = principal.toString();
}

1.2 Authentication

首先看一下源码:

package org.springframework.security.core;

public interface Authentication extends Principal, Serializable { 
    Collection<? extends GrantedAuthority> getAuthorities();  

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    boolean isAuthenticated(); 

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

从这个接口中,我们可以得到用户身份信息,密码,细节信息,认证信息,以及权限列表,具体的详细解读如下:

  • getAuthorities(),权限列表,通常是代表权限的字符串列表;
  • getCredentials(),密码信息,由用户输入的密码凭证,认证之后会移出,来保证安全性;
  • getDetails(),细节信息,Web应用中一般是访问者的ip地址和sessionId;
  • getPrincipal(), 最重要的身份信息,一般返回UserDetails的实现类;

官方文档里说过,当用户提交登陆信息时,会将用户名和密码进行组合成一个实例UsernamePasswordAuthenticationToken,而这个类是Authentication的一个常用的实现类,用来进行用户名和密码的认证,类似的还有RememberMeAuthenticationToken,它用于记住我功能。

1.3 UserDetails

上文提到了UserDetails接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,具体的实现类对它进行了扩展,首先来看一下源码:

public interface UserDetails extends Serializable {

   Collection<? extends GrantedAuthority> getAuthorities();

   String getPassword();

   String getUsername();

   boolean isAccountNonExpired();

   boolean isAccountNonLocked();

   boolean isCredentialsNonExpired();

   boolean isEnabled();
}

它和Authentication接口类似,都包含了用户名,密码以及权限信息,而区别就是Authentication中的getCredentials来源于用户提交的密码凭证,而UserDetails中的getPassword取到的则是用户正确的密码信息,认证的第一步就是比较两者是否相同,除此之外,Authentication#getAuthorities是认证用户名和密码成功之后,由UserDetails#getAuthorities传递而来。而Authentication中的getDetails信息是经过了AuthenticationProvider认证之后填充的。

1.4 UserDetailsService

public interface UserDetailsService {
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetailsService 只有一个方法,就是从特定的地方(一般是从数据库中)加载用户信息。

1.5 AuthenticationManager

public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

AuthenticationManager接口只包含一个方法,那就是认证,它是认证相关的核心接口,也是发起认证的出发点。实际业务中可能根据不同的信息进行认证,所以Spring推荐通过实现AuthenticationManager接口来自定义自己的认证方式.Spring提供了一个默认的实现,ProviderManager。

ProviderManager与认证相关的源码:

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {

    // 维护一个AuthenticationProvider列表
    private List<AuthenticationProvider> providers = Collections.emptyList();
          
    public Authentication authenticate(Authentication authentication)
          throws AuthenticationException {
       Class<? extends Authentication> toTest = authentication.getClass();
       AuthenticationException lastException = null;
       Authentication result = null;

       // 依次认证
       for (AuthenticationProvider provider : getProviders()) {
          if (!provider.supports(toTest)) {
             continue;
          }
          try {
             result = provider.authenticate(authentication);

             if (result != null) {
                copyDetails(authentication, result);
                break;
             }
          }
          ...
          catch (AuthenticationException e) {
             lastException = e;
          }
       }
       // 如果有Authentication信息,则直接返回
       if (result != null) {
			if (eraseCredentialsAfterAuthentication
					&& (result instanceof CredentialsContainer)) {
              	 //移除密码
				((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不是自己处理身份验证请求,它将委托给配置的AuthenticationProvider列表,按照顺序进行依次认证,每个provider都会尝试认证,或者通过简单地返回null来跳过验证。如果所有实现都返回null,那么ProviderManager将抛出一个ProviderNotFoundException

1.6 AuthenticationProvider

public interface AuthenticationProvider {

    Authentication authenticate(Authentication authentication)
        throws AuthenticationException;


    boolean supports(Class<?> authentication);
}

AuthenticationProvider接口提供了两个方法,一个是真正的认证,另一个是满足什么样的身份信息才进行如上认证。
Spring 提供了几种AuthenticationProvider的实现:

  • DaoAuthenticationProvider从数据库中读取用户信息验证身份
  • AnonymousAuthenticationProvider匿名用户身份认证
  • RememberMeAuthenticationProvider已存cookie中的用户信息身份认证
  • AuthByAdapterProvider使用容器的适配器验证身份
  • CasAuthenticationProvider根据Yale中心认证服务验证身份,用于实现单点登陆
  • JaasAuthenticationProvider从JASS登陆配置中获取用户信息验证身份
  • RemoteAuthenticationProvider根据远程服务验证用户身份
  • RunAsImplAuthenticationProvider对身份已被管理器替换的用户进行验证
  • X509AuthenticationProvider从X509认证中获取用户信息验证身份
  • TestingAuthenticationProvider单元测试时使用

当然也可以自己实现AuthenticationProvider接口来自定义认证。
这里我们基于最常用的DaoAuthenticationProvider来详细解释一下:

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    //密码加解密算法
    private PasswordEncoder passwordEncoder;
    //用户信息dao
    private UserDetailsService userDetailsService;

    //检查用户名和密码是否匹配
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            throw new BadCredentialsException(messages.getMessage(
                "AbstractUserDetailsAuthenticationProvider.badCredentials",
                "Bad credentials"));
        }
        //用户提交的密码凭证
        String presentedPassword = authentication.getCredentials().toString();
        //比较两个密码
        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            throw new BadCredentialsException(messages.getMessage(
                "AbstractUserDetailsAuthenticationProvider.badCredentials",
                "Bad credentials"));
        }
    }


    //获取用户信息
    protected final UserDetails retrieveUser(String username,
                                             UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
        UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    }
}

在Spring Security中。提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken,而根据用户名加载用户的任务则是交给了UserDetailsService,在DaoAuthenticationProvider中,对应的方法便是retrieveUser,虽然有两个参数,但是retrieveUser只有第一个参数起主要作用,返回一个UserDetails。还需要完成UsernamePasswordAuthenticationTokenUserDetails密码的比对,这便是交给additionalAuthenticationChecks方法完成的,如果这个void方法没有抛异常,则认为比对成功。

1.7 总结

为了方便理解,放上我画的uml图。
这里写图片描述
以及整个认证过程的:

Web AuthenticationManager DaoAuthenticationProvider UserDetailsService 1.Authentication() 2.Authentication() 3.loadUserByUsername() 4.UserDetails 5.返回认证结果 6.返回认证结果 Web AuthenticationManager DaoAuthenticationProvider UserDetailsService

参考: https://www.cnkirito.moe/spring-security-1/

  • 45
    点赞
  • 183
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring Security是一个在Java应用程序中提供身份验证和授权的框架。它通过使用各种身份验证和授权技术,帮助开发人员实现应用程序的安全性。 在Spring Security中,身份验证包括验证用户的身份以确保其是合法的,并且授权包括确定用户是否有权访问特定功能或资源。以下是Spring Security中的一些关键概念和用法: 1. 身份验证:Spring Security提供了许多身份验证机制,例如基于表单的身份验证、基于HTTP基本身份验证、基于LDAP的身份验证等。开发人员可以选择适合他们应用程序需求的身份验证机制。 2. 授权:Spring Security使用许可(Permission)和角色(Role)的概念来控制访问权限。可以使用特定的注解或编程方式将这些权限和角色应用到方法或URL上。 3. 认证和授权流程:Spring Security认证和授权过程中使用了一系列的过滤器和提供者。它们分别负责处理身份验证和授权的不同方面。开发人员可以根据需要定制这些组件来满足自己的应用程序需求。 4. AccessDecisionManager:这是Spring Security中的一个重要组件,用于决定用户是否有权限访问特定的资源或功能。开发人员可以实现自己的AccessDecisionManager来根据自己的逻辑进行权限决策。 5. UserDetails:在Spring Security中,用户信息通过UserDetails接口进行封装。开发人员可以根据自己的需求实现自定义的UserDetails接口,并提供用户的身份验证和授权信息。 6. 匿名认证Spring Security支持为匿名用户建立一个匿名Authentication对象。这样,无需再对匿名用户进行额外的验证,可以直接将其当作正常的Authentication对象来使用。 综上所述,Spring Security提供了全面的身份验证和授权机制来保护应用程序的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值