Spring Security 系列(1)

Spring Security

什么是 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 的架构应该如下

image

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

登陆成功流程大致如下图

img

  1. 当用户提交他们的凭据时, AbstractAuthenticationProcessingFilter 将从被验证的 HttpServletRequest 处创建一个 AuthenticationAuthentication 类型的创建工作则依赖于
    AbstractAuthenticationProcessingFilter 的子类。例如
    UsernamePasswordAuthenticationFilter 从已提交的 HttpServletRequest 中获取用户名密码并创建
    UsernamePasswordAuthenticationToken

  2. 接下来,将 Authentication 传递给 AuthenticationManager 进行身份验证。

  3. 如果身份验证失败,

    • 清除 SecurityContextHolder
    • 调用 RememberMeServices.loginFail。如果没有配置 remeberme ,则不会进行任何操作
    • 调用 AuthenticationFailureHandler
  4. 如果身份验证成功,则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)的认证流程。

这为登陆失败时的流程:

image

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

求和的小熊猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值