【问题标题】:Basics of Spring SecuritySpring Security 基础知识
【发布时间】:2021-07-23 13:07:54
【问题描述】:

什么是 Spring Security 的基础知识,即 Spring 如何在内部设置安全性。为 Spring Security 提供开箱即用的所有相关 bean?

【问题讨论】:

    标签: java spring spring-boot spring-mvc spring-security


    【解决方案1】:

    我将首先解释如何将 Spring Security 引入您的应用程序。

    只需将以下依赖项添加到您的应用程序。现在,当您运行应用程序时,默认情况下会实现 spring 安全性。 (截至 2021 年 4 月,版本可能会在未来发生变化)

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>5.4.5</version>
    </dependency>
    

    仔细查看控制台,您将看到为默认用户生成的密码:user。密码是您需要使用的哈希值。

    当您现在从应用程序访问任何 URL 时,您将被 Postman 限制。在您的浏览器中,您将看到一个登录页面,您需要在其中输入此用户名和密码,然后您将进入您的 URL。这设置了内置的 Spring Security。

    但是幕后发生了什么?

    我将通过提醒您Spring中的Servlet和Filters和DispatcherServlet来回答这个问题。

    DispatcherServlet 是 Spring MVC 的基础,它将请求转发到您的控制器。基本上,DispatcherServlet 也是一个 servlet。

    我可以在 DispatcherServlet 之前创建一个过滤器链,并在转发请求以访问我的 DispatcherServlet 和我的控制器之前检查我的身份验证和授权请求。这样,我可以为我的应用程序引入安全性。这正是 Spring Security 所做的。

    下面的链接非常巧妙地突出了 DispatcherServlet 之前的所有过滤器以及这些过滤器的重要性。请参考以下链接:

    How Spring Security Filter Chain works

    现在,我们需要了解什么是身份验证和授权:

    1. 身份验证 - 任何使用您的应用程序的人都需要一些信息,并且您需要验证该用户的用户名、密码以允许他访问您的应用程序。如果他的用户名或密码错误,则表示他未通过身份验证。
    2. 授权 - 一旦用户通过身份验证,您的应用程序的某些 URL 可能只允许管理员用户而不是普通用户。这称为授权用户根据其角色访问您的应用程序的某些部分。

    让我们看一下过滤器链中一些重要的Spring过滤器:

    • BasicAuthenticationFilter: 尝试在请求中查找 Basic Auth HTTP 标头,如果找到,则尝试使用标头的用户名和密码对用户进行身份验证。

    • UsernamePasswordAuthenticationFilter: 尝试查找用户名/密码请求参数/POST 正文,如果找到,则尝试使用这些值对用户进行身份验证。

    • DefaultLoginPageGeneratingFilter:如果您没有明确禁用该功能,则会为您生成登录页面。这个过滤器是你在启用 Spring Security 时获得默认登录页面的原因。

    • DefaultLogoutPageGeneratingFilter:如果您没有明确禁用该功能,则会为您生成一个注销页面。

    • FilterSecurityInterceptor:您的授权。

    默认情况下,这些过滤器会为您提供您在浏览器上看到的登录页面。此外,它们还提供注销页面、使用基本身份验证或表单登录登录的能力,以及防止 CSRF 攻击。

    请记住,在您的 pom.xml 中添加 Spring Security 之后开始的登录页面。这是因为下面的课程:

    public abstract class WebSecurityConfigurerAdapter implements
                    WebSecurityConfigurer<WebSecurity> {
    
        protected void configure(HttpSecurity http) throws Exception {
                http
                    .authorizeRequests()
                        .anyRequest().authenticated()
                        .and()
                    .formLogin().and()
                    .httpBasic();
            }
    }
    

    这个 WebSecurityConfigurerAdapter 类是我们扩展的,我们覆盖了它的配置方法。如上所述,所有请求都需要通过表单登录方法进行基本身份验证。这个登录页面是我们访问 URL 时看到的 Spring 提供的默认页面。

    现在,下一个问题出现了,如果我们想自己做这个配置怎么办?以下主题正是讨论了这一点:

    如何配置 Spring Security?

    要配置 Spring Security,我们需要有一个 @Configuration, @EnableWebSecurity 类,它扩展了 WebSecurityConfigurerAdapter 类。

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
          http
            .authorizeRequests()
              .antMatchers("/", "/home").permitAll()
              .anyRequest().authenticated()
              .and()
           .formLogin()
             .loginPage("/login")
             .permitAll()
             .and()
          .logout()
            .permitAll()
            .and()
          .httpBasic();
      }
    }
    

    您必须执行上述配置。现在,您可以进行特定的安全配置,即允许所有 URL,哪些需要进行身份验证,应用程序将执行的身份验证类型是什么以及特定 URL 上允许的角色是什么。

    因此,基本上,您所有的身份验证和授权信息都在此处配置。关于 CORS、CSRF 和其他漏洞的其他配置也在这里完成,但这超出了基础知识的范围。

    在上面的示例中,任何用户都可以访问 //home 的所有请求,即任何人都可以访问它们并获得响应,但其他请求需要认证。此外,我们允许表单登录,即当访问除 //home 之外的任何请求时,用户将看到一个登录页面,他将在其中输入他的用户名和密码,并且该用户名/密码将使用基本身份验证进行身份验证,即发送 HTTP Basic Auth Header 进行身份验证。

    到目前为止,我们已经添加了 Spring Security,保护了我​​们的 URL,配置了 Spring Security。但是,我们将如何检查用户名和密码以进行身份​​验证?下面讨论这个:

    你需要指定一些@Beans 才能让 Spring Security 工作。为什么需要一些豆子? 因为 Spring Container 需要这些 bean 来实现底层的安全性。

    您需要提供这两个 bean – UserDetailsS​​ervice 和 PasswordEncoder。

    UserDetailsS​​ervice 这负责将您的用户提供给 Spring 容器。用户可以出现在您的数据库、内存中,任何地方。例如:它可以与用户名、密码、角色和其他列一起存储在用户表中。

    @Bean
    public UserDetailsService userDetailsService() {
        return new MyUserDetailsService();
    }
    

    上面,我们提供了我们的自定义 MyUserDetailsS​​ervice,它必须是 Spring 容器的 UserDetailsS​​ervice 子项,以识别其用途。以下是示例实现:

    public class MyDatabaseUserDetailsService implements UserDetailsService {
    
            UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
             //  Load the user from the users table by username. If not found, throw UsernameNotFoundException.
             // Convert/wrap the user to a UserDetails object and return it.
            return someUserDetails;
        }
    }
    
    public interface UserDetails extends Serializable {
    
        String getUsername();
    
        String getPassword();
    
        // isAccountNonExpired,isAccountNonLocked,
        // isCredentialsNonExpired,isEnabled
    }
    

    你看,UserDetailsS​​ervice 应该为容器提供 UserDetails 对象。

    默认情况下,Spring 提供了 UserDetailsS​​ervice 的这些实现:

    1. JdbcUserDetailsManager- 这是一个基于 JDBC 的 UserDetailsS​​ervice。您可以对其进行配置以匹配您的用户表/列结构。

    2。 InMemoryUserDetailsManager- 将所有用户详细信息保存在内存中。这通常用于测试目的。

    3. org.springframework.security.core.userdetail.User– 这是自定义应用程序中最常用的。您可以在您的用户对象的自定义实现上扩展此 User 类。

    现在,如上所述,如果有任何请求到达并需要进行身份验证,那么由于我们有 UserDetailsS​​ervice,我们将从 UserDetailsS​​ervice 返回的 UserDetails 对象中获取用户,该对象已发送请求并可以对其进行身份验证使用从我们的 UserDetailsS​​ervice 收到的用户名/密码发送。

    这样,用户就通过了身份验证。

    注意:从用户那里收到的密码会自动进行哈希处理。因此,如果我们没有来自 UserDetailsS​​ervice 的密码哈希表示,即使密码正确也会失败。

    为了防止这种情况,我们向容器提供 PasswordEncoder bean,它将对 UserDetails 对象中的密码应用 PasswordEncoder 指定的散列算法并为其生成散列。然后,它会检查哈希密码并对用户进行身份验证或失败。

    PasswordEncoder- 出于安全目的,这会提供您密码的哈希值。 为什么?您不能/不应该处理普通密码。这超出了 Spring Security 的目的。更好的是,使用任何算法对其进行哈希处理。

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    

    现在,您可以在应用程序的任何位置自动装配此 PasswordEncoder。

    AuthenticationProvider-

    在某些情况下,我们无法访问用户的密码,但其他第三方以某种奇特的方式存储了我们的用户信息。

    在这些情况下,我们需要向 Spring 容器提供 AuthenticationProvider bean。一旦容器有了这个对象,它将尝试使用我们提供的实现进行身份验证,以向第三方进行身份验证,这将为我们提供 UserDetails 对象或我们可以从中获取 UserDetails 对象的任何其他对象。

    一旦获得,这意味着我们已通过身份验证,我们将发送回一个带有我们的用户名、密码和权限/角色的 UsernamePasswordAuthenticationToken。如果没有获取到,我们可以抛出异常。

        @Bean
        public AuthenticationProvider authenticationProvider() {
            return new MyAuthenticationProvider();
        }
    
    

    AuthenticationProvider 主要由一个方法组成,基本实现可能如下所示:

    public class MyAuthenticationProvider implements AuthenticationProvider {
    
            Authentication authenticate(Authentication authentication)  
                    throws AuthenticationException {
                String username = authentication.getPrincipal().toString(); 
                String password = authentication.getCredentials().toString();
    
                User user = callThirdPartyService(username, password); 
                if (user == null) {                                     
                    throw new AuthenticationException("Incorrect username/password");
                }
                return new UserNamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
            }
    }
    

    这就是 Spring Security 基础或底层功能的全部内容,以及我们如何利用这些来定制我们的安全实现。您可以在任何地方找到示例。更高级的主题,如 JWT、Oauth2 实施、CSRF 预防、CORS 津贴超出了范围。

    【讨论】: