Spring 安全的基础知识

Kno*_*ads 1 java spring spring-mvc spring-security spring-boot

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

Kno*_*ads 5

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

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

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>5.4.5</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

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

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

但是引擎盖下发生了什么?

我将通过提醒您 Spring 中的 Servlets and Filters 和 DispatcherServlet 来回答它。

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

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

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

Spring Security Filter Chain 如何工作

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

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

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

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

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

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

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

• FilterSecurityInterceptor:是否授权。

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

请记住,在将 Spring Security 添加到 pom.xml 之后开始的登录页面。这是由于以下课程而发生的:

public abstract class WebSecurityConfigurerAdapter implements
                WebSecurityConfigurer<WebSecurity> {

    protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .formLogin().and()
                .httpBasic();
        }
}
Run Code Online (Sandbox Code Playgroud)

这个 WebSecurityConfigurerAdapter 类是我们扩展的类,我们覆盖了它的 configure 方法。如上所述,所有请求都需要通过表单登录方法进行基本身份验证。这个登录页面是我们访问 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();
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

到目前为止,我们已经添加了 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();
}
Run Code Online (Sandbox Code Playgroud)

上面,我们提供了我们的自定义 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;
    }
}
Run Code Online (Sandbox Code Playgroud)
public interface UserDetails extends Serializable {

    String getUsername();

    String getPassword();

    // isAccountNonExpired,isAccountNonLocked,
    // isCredentialsNonExpired,isEnabled
}
Run Code Online (Sandbox Code Playgroud)

你看, 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();
}
Run Code Online (Sandbox Code Playgroud)

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

AuthenticationProvider-

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

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

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

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

Run Code Online (Sandbox Code Playgroud)

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());
        }
}
Run Code Online (Sandbox Code Playgroud)

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