Spring Security


文章目录

  • Spring Security
  • 什么是 Spring Security
  • Spring Security 的核心组件
  • Spring Security 的加密工具
  • Spring Security 的架构
  • Spring Security 的使用
  • 引入 Spring Security
  • 添加密码加密器
  • 配置安全策略
  • 登陆成功的处理与配置
  • 通过权限控制访问
  • 进行 Token 配置
  • 使用自定义的 UserDetailsService
  • 使用自定义的 AuthenticationEntryPoint
  • Spring Security 自带的 AuthenticationEntryPoint


什么是 Spring Security

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security 的核心组件

本节介绍Spring Security在Servlet身份验证中使用的主要架构组件。如果需要解释这些部分如何组合在一起的具体流程,请查看特定于身份验证机制的部分。

  • SecurityContextHolder - SecurityContextHolder是Spring Security存储被身份验证者的详细信息的地方。
  • SecurityContext - 从 SecurityContextHolder 获取,包含当前经过身份验证的用户的身份验证。
  • Authentication - 可以是 AuthenticationManager 的输入,以提供用户提供的用于身份验证的凭据(Token),也可以是 SecurityContext 中的当前用户。
  • GrantedAuthority - 授予身份验证主体的权限(即角色、作用域等)
  • AuthenticationManager - 定义Spring Security过滤器如何执行身份验证的API。
  • ProviderManager - AuthenticationManager 最常见的实现。
  • AuthenticationProvider - 提供程序管理器用于执行特定类型的身份验证。
  • Request Credentials with AuthenticationEntryPoint - 用于从客户端请求凭据(即重定向到登录页面、发送 WWW 身份验证响应等)
  • AbstractAuthenticationProcessingFilter - 用于身份验证的基本过滤器。这也很好地了解了身份验证的高级流程以及各个部分如何协同工作。

关系的简要示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sfrsO9wC-1654844540950)(https://app.yinxiang.com/FileSharing.action?hash=1/c73daea197b506c37fd4b8a220689411-31431)]

SecurityContextHolder 示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JIauoIE6-1654844540952)(https://app.yinxiang.com/FileSharing.action?hash=1/e5736a02d5ccb739ae0bc4698fa58f48-22163)]

Spring Security 的加密工具

  • BCryptPasswordEncoder(bcrypt 算法加密)
  • Argon2PasswordEncoder(argon2 算法加密)
  • Pbkdf2PasswordEncoder(pbkdf2 算法加密)
  • SCryptPasswordEncoder(scrypt 算法加密)

Spring Security 的架构

这是 WebSecurityConfigurerAdapter 的初始化加载流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y6FcKHhY-1654844540952)(https://app.yinxiang.com/FileSharing.action?hash=1/ad820c1970141c44e14862330e49d6d5-73403)]

从上侧流程图我们可以大致了解 Spring Security 的架构应该如下

spring security和shori选哪个 spring security 系列_ide

其中 FilterChain 中包含各种各样的 filter

登陆成功流程大致如下图

spring security和shori选哪个 spring security 系列_身份验证_02

  1. 当用户提交他们的凭据时, AbstractAuthenticationProcessingFilter 将从被验证的 HttpServletRequest 处创建一个 AuthenticationAuthentication 类型的创建工作则依赖于
    AbstractAuthenticationProcessingFilter 的子类。例如
    UsernamePasswordAuthenticationFilter 从已提交的 HttpServletRequest 中获取用户名密码并创建
    UsernamePasswordAuthenticationToken
  2. 接下来,将 Authentication 传递给 AuthenticationManager 进行身份验证。
  3. 如果身份验证失败,
  • 清除 SecurityContextHolder
  • 调用 RememberMeServices.loginFail。如果没有配置 remeberme ,则不会进行任何操作
  • 调用 AuthenticationFailureHandler
  1. 如果身份验证成功,则Success。
  • SessionAuthenticationStrategy 收到新登录通知。
  • SecurityContextHolder 设置 Authentication 。稍后 SecurityContextPersistenceFilterSecurityContext 保存到HttpSession 中.
  • 调用 RememberMeServices.loginSuccess 。如果没有配置 remeberme ,则不会进行任何操作
  • ApplicationEventPublisher 发布 InteractiveAuthenticationSuccessEvent 事件
  • 调用 AuthenticationSuccessHandler

Spring Security 的使用

引入 Spring Security

pom.xml

引入 Spring Security 时我们需要在 pom.xml 中添加

<!--Spring Security 包引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--Spring Boot Web 包引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Spring Boot 测试包引入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--Spring Security 测试包引入-->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

application.yml

在 application.yml 中编写配置文件

server:
  port: 8088 # 服务器端口

配置 WebSecurityConfigurerAdapter

// Step1: 创建自己的 WebSecurityConfigurerAdapter
@Configuration
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    // Step2: 重写 configure 方法
    @Override
    public void configure(HttpSecurity http){
       
    }
}

添加密码加密器

配置类 WebSecurityConfigurerAdapter

// 注入密码加密器
@Bean
PasswordEncoder passwordEncoder(){
    // return new BCryptPasswordEncoder();
    // return new Argon2PasswordEncoder();
    // return new SCryptPasswordEncoder();
    return new Pbkdf2PasswordEncoder();
}

使用加密器对密码进行加密以及密码之间的匹配

String password = "RawPassword";
String pwdCrypt = passwordEncoder.encode(password);
passwordEncoder.matches(password,pwdCrypt);

配置安全策略

登陆成功的处理与配置

配置 WebSecurityConfigurerAdapter

@Override
    public void configure(HttpSecurity http){
        try {
            http.formLogin()
                    .usernameParameter("usr")// 设定登陆时用户名的参数名,默认为 username
                    .passwordParameter("pwd")// 设定登陆时密码的参数名,默认为 password
                    .loginPage("login.html") // 设定登陆页面
                    .loginProcessingUrl("/login") // 处理登陆的 url
                    .successForwardUrl("/loginSuccess") // 登陆成功后跳转的 url
                    .successHandler(new AuthenticationSuccessHandler() { // 登陆成功的处理器
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        }
                    }).failureForwardUrl("/loginFailure") // 失败跳转的 url
                    .failureHandler(new AuthenticationFailureHandler() { // 失败的请求处理器
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                        }
                    });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
通过权限控制访问

配置 WebSecurityConfigurerAdapter

@Override
    public void configure(HttpSecurity http){
        try {
            http.authorizeRequests()
                    .antMatchers(HttpMethod.POST,"/login").permitAll()
                    .antMatchers(HttpMethod.OPTIONS).permitAll()
                    .antMatchers("/status").permitAll() // 允许所有请求通过访问
                    .regexMatchers("*.png").permitAll() // 通过正则表达式进行匹配
                    .antMatchers("/userResource").hasAuthority("USER") // 具有某个权限后方可访问
                    .antMatchers("/userResource1").hasAnyAuthority("SYS","USER") // 具有任意一个权限后方可访问
                    .antMatchers("/roleResource").hasRole("role1") // 具有某个角色后方可访问,角色的权限名称为 “ROLE_” 前缀加上角色名称
                    .antMatchers("/roleResource1").hasAnyRole("role1","role2")// 具有任意一个角色后方可访问
                    .antMatchers("/accessAuthority").access("hasAnyAuthority(SYS,USER)")// 具有任意一个权限后方可访问
                    .antMatchers("/accessRoles").access("hasAnyRole(role1,role2)")// 具有任意一个角色后方可访问
                    .anyRequest().authenticated(); // 其余任何请求都需要经过访问
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
进行 Token 配置

配置 WebSecurityConfigurerAdapter

@Override
    public void configure(HttpSecurity http){
        try {
            http.rememberMe().rememberMeParameter("remeberme")
                    .tokenRepository(new InMemoryTokenRepositoryImpl()) // 设置 Token 仓库,用于存储,更新,和获得 Token
                    .userDetailsService(username -> // 配置验证时的 UserDetailService
                            new User(username,"pwd", AuthorityUtils.commaSeparatedStringToAuthorityList("sys")))
                    .tokenValiditySeconds(1800);//token 有效时间
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用自定义的 UserDetailsService

UserDetailsService 接口

创建 MyUserDetailsService 类并且实现 UserDetailsService 接口

@Service
public class MyUserDetailsService implements UserDetailsService{
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        String password = "pwd";
        String pwdCrypt = passwordEncoder.encode(password);// 模拟从数据中取出密码
        List<SimpleGrantedAuthority> authorityList = new ArrayList<>();
        authorityList.add(new SimpleGrantedAuthority("ROLE_user"));
        // 这里只是模拟,实际可以与数据库进行交互获得用户权限
        UserDetails user = new User(username,pwdCrypt,authorityList);
        return user;
    }
}

配置 WebSecurityConfigurerAdapter

// 注入创建的 UserService
    @Bean
    public MyUserDetailsService myUserDetailsService(){
        return new MyUserDetailsService();
    }
    
    // 重写 Configure 方法,使配置生效
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService()) // 注入 UserDetailsService
                .passwordEncoder(passwordEncoder());
    }
    // 将代理 AuthenticationManager 注册成 Bean,供直接使用
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

Ps:
一般来说 UserDetails 一般是由 UserDetailsService 来返回的。DaoAuthenticationProvider 验证 UserDetails 并返回一个 Authentication 对象

使用自定义的 AuthenticationEntryPoint

AuthenticationEntryPoint 他所建模的概念是:“认证入口点”。它在用户请求处理过程中遇到认证异常时,被ExceptionTranslationFilter用于开启特定认证方案(authentication schema)的认证流程。

这为登陆失败时的流程:

spring security和shori选哪个 spring security 系列_身份验证_03

  1. 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的其余部分
  2. 如果用户未通过身份验证或它是一个AuthenticationException,则开始身份验证。
  • 清除 SecurityContextHolder
  • HttpServletRequest保存在RequestCache. 当用户成功认证后,RequestCache用于重放原始请求。
  • AuthenticationEntryPoint用于从客户端请求凭据。例如,它可能会重定向到登录页面或发送WWW-Authenticate标头。
  1. 否则,如果是AccessDeniedException,则拒绝访问。被AccessDeniedHandler调用来处理拒绝访问。

AuthenticationEntryPoint 接口

创建一个 MyAuthenticationEntryPoint 类,并实现 AuthenticationEntryPoint 接口

@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()); // 自定义处理返回信息
    }
}

配置 WebSecurityConfigurerAdapter

@Autowired
    MyAuthenticationEntryPoint authenticationEntryPoint; // 注入自己实现的 EntryPoint
    // Step2: 重写 configure 方法
    @Override
    public void configure(HttpSecurity http){
        try {
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
Spring Security 自带的 AuthenticationEntryPoint
  • Http403ForbiddenEntryPoint
    设置响应状态字为403,并非触发一个真正的认证流程。通常在一个预验证(pre-authenticated authentication)已经得出结论需要拒绝用户请求的情况被用于拒绝用户请求。
  • HttpStatusEntryPoint
    设置特定的响应状态字,并非触发一个真正的认证流程。
  • LoginUrlAuthenticationEntryPoint
    根据配置计算出登录页面url,将用户重定向到该登录页面从而开始一个认证流程。
  • BasicAuthenticationEntryPoint
    对应标准Http Basic认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Basic realm="xxx"触发标准Http Basic认证流程。
  • DigestAuthenticationEntryPoint
    对应标准Http Digest认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Digest realm="xxx"触发标准Http Digest认证流程。
  • DelegatingAuthenticationEntryPoint
    这是一个代理,将认证任务委托给所代理的多个AuthenticationEntryPoint对象,其中一个被标记为缺省AuthenticationEntryPoint。