Spring Security——认证、授权的工作原理

目录

  • 一、Spring Security 原理
  • 二、Spring Security认证流程 (源码跟踪)
    • 1、AuthenticationProvider
    • 2、UserDetailsService
    • 3、PasswordEncoder
  • 三、Spring Security授权流程 (源码跟踪)
    • 1、授权决策

一、Spring Security 原理

跳转到目录

  • 是基于过滤器链来拦截用户发送的请求;

  • Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截, 校验每个请求是否能够访问它所期望的资源。根据前边知识的学习,可以通过FilterAOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。

  • 当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此类,下图是Spring Security过虑器链结构图

Spring Security——认证、授权的工作原理_第1张图片
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager决策(授权)管理器 (AccessDecisionManager)进行处理,下图是FilterChainProxy相关类的UML图示。

Spring Security——认证、授权的工作原理_第2张图片
Spring Security——认证、授权的工作原理_第3张图片

一、Spring Security认证流程 (源码跟踪)

跳转到目录
Spring Security——认证、授权的工作原理_第4张图片
根据上面的时序图, 在程序中找到UsernamePasswordAuthenticationFilterDaoAuthenticationProvider两个类, 通过断点来分析上面时序图的流程;

1、首先进去登录页面, 输入正确的账号密码 zhangsan, 123
Spring Security——认证、授权的工作原理_第5张图片
2、因为输入账号密码后, 会进入到AbstractAuthenticationProcessingFilterdoFilter方法,
Spring Security——认证、授权的工作原理_第6张图片
Spring Security——认证、授权的工作原理_第7张图片

3、认证器(AuthenticationManager)主要委托DaoAuthenticationProvider来认证用户的账号密码; 找到该类的retrieveUser方法
Spring Security——认证、授权的工作原理_第8张图片
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); 这一步, 会调用我们编写的userDetailsService方法, 从数据库中取出用户的账号密码;
Spring Security——认证、授权的工作原理_第9张图片
Spring Security——认证、授权的工作原理_第10张图片
4、对用户输入的账号密码 和 数据库中的账号密码, 进行匹配, 会进入到DaoAuthenticationProvider的父类AbstractUserDetailsAuthenticationProvider进行判断操作
Spring Security——认证、授权的工作原理_第11张图片
进入additionalAuthenticationChecks方法, 进行账号密码的匹配
Spring Security——认证、授权的工作原理_第12张图片
Spring Security——认证、授权的工作原理_第13张图片
此时就登录成功;


认证核心组件的大体关系如下:
Spring Security——认证、授权的工作原理_第14张图片

1、AuthenticationProvider

跳转到目录
Spring Security——认证、授权的工作原理_第15张图片
Spring Security——认证、授权的工作原理_第16张图片

2、UserDetailsService

跳转到目录

  • 认识UserDetailsService

现在咱们现在知道DaoAuthenticationProvider处理了web表单的认证逻辑,认证成功后既得到一个Authentication(UsernamePasswordAuthenticationToken实现),里面包含了身份信息(Principal)。这个身份 信息就是一个 Object ,大多数情况下它可以被强转为UserDetails对象。

DaoAuthenticationProvider中包含了一个UserDetailsService实例,它负责根据用户名提取用户信息 UserDetails(包含密码),而后DaoAuthenticationProvider会去对比UserDetailsService提取的用户密码与用户提交 的密码是否匹配作为认证成功的关键依据,因此可以通过将自定义的 UserDetailsService 公开为spring bean来定 义自定义身份验证。

在这里插入图片描述

  • 很多人把DaoAuthenticationProviderUserDetailsService的职责搞混淆,其实UserDetailsService只负责从特定的地方(通常是数据库)加载用户信息,仅此而已。而DaoAuthenticationProvider的职责更大,它完成完整的认证流程,同时会把UserDetails填充至Authentication。

Spring Security——认证、授权的工作原理_第17张图片

  • 它和Authentication接口很类似,比如它们都拥有username,authorities。Authentication的getCredentials()与 UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户实际存储的密码,认证 其实就是对这两者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而形 成的。还记得Authentication接口中的getDetails()方法吗?其中的UserDetails用户详细信息便是经过了 AuthenticationProvider认证之后被填充的。

  • 通过实现UserDetailsService和UserDetails,我们可以完成对用户信息获取方式以及用户信息字段的扩展。

  • Spring Security提供的InMemoryUserDetailsManager(内存认证),JdbcUserDetailsManager(jdbc认证)就是 UserDetailsService的实现类,主要区别无非就是从内存还是从数据库加载用户。

测试:
自定义UserDetailsService

@Service
public class MyUserDetailService implements UserDetailsService {

    // 根据账号查询信息
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        // 将来连接数据库根据账号来查询用户信息
        // 现在先模拟
        System.out.println("username = " + username);
        UserDetails userDetails = User.withUsername("zhangsan1").password("123").authorities("p1").build();
        return userDetails;
    }
}

屏蔽安全配置类中UserDetailsService的定义

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 配置用户信息服务
//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
//        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
//        return manager;
//    }

重启工程,请求认证,MyUserDetailsService的loadUserByUsername方法被调用 ,查询用户信息。

3、PasswordEncoder

跳转到目录
认识PasswordEncoder

DaoAuthenticationProvider认证处理器通过UserDetailsService获取到UserDetails后,它是如何与请求 Authentication中的密码做对比呢?

在这里Spring Security为了适应多种多样的加密类型,又做了抽象,DaoAuthenticationProvider通过 PasswordEncoder接口的matches方法进行密码的对比,而具体的密码对比细节取决于实现:

Spring Security——认证、授权的工作原理_第18张图片
而Spring Security提供很多内置的PasswordEncoder,能够开箱即用,使用某种PasswordEncoder只需要进行如 下声明即可,如下

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

NoOpPasswordEncoder采用字符串匹配方法,不对密码进行加密比较处理,密码比较流程如下:

1、用户输入密码(明文 )
2、DaoAuthenticationProvider获取UserDetails(其中存储了用户的正确密码)
3、DaoAuthenticationProvider使用PasswordEncoder对输入的密码和正确的密码进行校验,密码一致则校验通 过,否则校验失败。

  • NoOpPasswordEncoder的校验规则拿 输入的密码和UserDetails中的正确密码进行字符串比较,字符串内容一致 则校验通过,否则校验失败

  • 实际项目中推荐使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等,感兴趣的大家可以看看这些PasswordEncoder的具体实现

使用BCryptPasswordEncoder
Spring Security——认证、授权的工作原理_第19张图片
2 测试BCrypt

@SpringBootTest
public class TestBCrypt {

    @Test
    public void testBCrypt() {
        // 对密码进行加密
        String hashpw = BCrypt.hashpw("123", BCrypt.gensalt());
        String hashpw2 = BCrypt.hashpw("456", BCrypt.gensalt());
        System.out.println("hashpw = " + hashpw);
        System.out.println("hashpw2 = " + hashpw2);

        // 校验密码
        boolean checkpw1 = BCrypt.checkpw("123", "$2a$10$QfQYXOtc/2oSgiuYi.9x6.8VcFZ4RuQOq7WmzwkkhXoiD.hB5swP.");
        boolean checkpw2 = BCrypt.checkpw("123", "$2a$10$ptyf4yyfbc1oL.OJPfSKMOk.hO4eRS1SQj44MBhhHnSZFrphjGHK.");
        System.out.println("checkpw1 = " + checkpw1);
        System.out.println("checkpw2 = " + checkpw2);
    }
}

3、修改安全配置类

/**
 * Description: 安全配置的内容包括:用户信息、密码编码器、安全拦截机制。
 *
 * @author zygui
 * @date Created on 2020/7/22 15:11
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 配置用户信息服务
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("$2a$10$QfQYXOtc/2oSgiuYi.9x6.8VcFZ4RuQOq7WmzwkkhXoiD.hB5swP.").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("$2a$10$LYa/9GkXYzhc/UjD7S/D5OWE2F7RXHVgANsDHC4XSp8OiEfi1Fk4e").authorities("p2").build());
        return manager;
    }

    // 对密码进行编码, 使用不加密的对比
//    @Bean
//    public PasswordEncoder passwordEncoder() {
//        return NoOpPasswordEncoder.getInstance();
//    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 配置安全拦截机制


    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .successForwardUrl("/login-success");//自定义登录成功的页面地址

    }
}

三、Spring Security授权流程 (源码跟踪)

跳转到目录
通过快速上手我们知道,Spring Security可以通过 http.authorizeRequests() 对web请求进行授权保护。Spring Security使用标准Filter建立了对web请求的拦截,最终实现对资源的授权访问。

Spring Security的授权流程如下:
Spring Security——认证、授权的工作原理_第20张图片
拿到当前访问资源所需要的权限信息用户信息中的权限信息作对比, 如果符合, 则授权成功;
Spring Security——认证、授权的工作原理_第21张图片

Spring Security——认证、授权的工作原理_第22张图片
AccessDecisionManager(访问决策管理器)的核心接口如下:

public interface AccessDecisionManager {
    void decide(Authentication var1, Object var2, Collection<ConfigAttribute> var3) throws AccessDeniedException, InsufficientAuthenticationException;

    boolean supports(ConfigAttribute var1);

    boolean supports(Class<?> var1);
}

这里着重说明一下decide的参数:

  • authentication:要访问资源的访问者的身份
  • object:要访问的受保护资源,web请求对应FilterInvocation
  • configAttributes:是受保护资源的访问策略,通过SecurityMetadataSource获取。

decide接口就是用来鉴定当前用户是否有访问对应受保护资源的权限。

1、授权决策

跳转到目录
AccessDecisionManager采用投票的方式来确定是否能够访问受保护资源。

Spring Security——认证、授权的工作原理_第23张图片
Spring Security——认证、授权的工作原理_第24张图片
Spring Security——认证、授权的工作原理_第25张图片
Spring Security——认证、授权的工作原理_第26张图片
注意: 默认是采用AffirmativeBased的方式

进入该类, 在decide方法下打断点
Spring Security——认证、授权的工作原理_第27张图片

Spring Security——认证、授权的工作原理_第28张图片

你可能感兴趣的:(SpringSecurity,Security,认证,授权,原理)