Spring Security的基础是什么?也就是Spring如何在内部设置安全性。为了让Spring Security开箱即用,需要提供哪些涉及到的bean呢?
Spring Security的基础是什么?也就是Spring如何在内部设置安全性。为了让Spring Security开箱即用,需要提供哪些涉及到的bean呢?
首先,我将解释如何将Spring Security引入您的应用程序中。
只需将以下依赖项添加到您的应用程序中。现在,当您运行应用程序时,默认情况下已实现spring security。(截至2021年4月,版本可能会在未来更改)
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.4.5</version>
</dependency>
仔细查看控制台,您会看到为默认用户生成的密码:user。该密码是一个需要使用的哈希值。
现在,当您访问应用程序中的任何URL时,您将受到Postman的限制。从您的浏览器中,您将看到一个登录页面,在该页面中您需要输入此用户名和密码,然后就可以访问您的URL。这是内置的Spring安全设置。
但是,在幕后发生了什么?
我将通过提醒您Servlets、Filters和Spring中的DispatcherServlet来回答这个问题。
DispatcherServlet是Spring MVC的基础,它将请求转发到您的控制器。基本上,DispatcherServlet也是一个servlet。
我可以在DispatcherServlet之前创建一系列过滤器,并在将请求转发到我的DispatcherServlet和控制器之前检查我的请求的身份验证和授权。这样,我就可以为我的应用程序引入安全性。这正是Spring Security所做的。
以下链接非常精细地突出显示了DispatcherServlet之前存在的所有过滤器以及这些过滤器的重要性。请参考以下链接:
现在,我们需要了解身份验证和授权是什么:
让我们来看看Spring Filter Chain中一些重要的Filter:
• BasicAuthenticationFilter:尝试查找请求中的Basic Auth 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();
}
}
这个WebSecurityConfigurerAdapter类是我们所扩展的,我们重写了它的configure方法。按照上述说明,所有请求都需要通过表单登录方式进行基本认证。这个登录页面是Spring提供的默认页面,我们在访问URL时看到了它。
现在,下一个问题出现了,如果我们想要自己配置怎么办?下面的主题正好讨论了这个问题:
如何配置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基本身份验证标头进行验证。
到目前为止,我们已经添加了Spring Security,保护了我们的URL,并配置了Spring Security。但是,如何检查用户名和密码是否已通过身份验证?以下内容讨论了这一点:
您需要指定一些@Bean才能使Spring Security正常工作。为什么需要一些bean? 因为Spring容器需要这些bean在幕后实现安全性。
您需要提供这两个bean——UserDetailsService和PasswordEncoder。
UserDetailsService – 负责向Spring容器提供您的用户。该用户可以存在于您的DB、内存或任何地方。例如,它可以存储在带有用户名、密码、角色和其他列的用户表中。
@Bean
public UserDetailsService userDetailsService() {
return new MyUserDetailsService();
}
以上,我们提供了我们定制的MyUserDetailsService,它必须是Spring容器的UserDetailsService子类以便识别其目的。以下是示例实现:
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
}
您看,UserDetailsService提供了包含UserDetails对象的容器。
默认情况下,Spring提供了以下这些UserDetailsService的实现:
1. JdbcUserDetailsManager- 这是一个基于JDBC的UserDetailsService。您可以配置它以匹配您的用户表/列结构。
2. InMemoryUserDetailsManager- 将所有用户详细信息保存在内存中。这通常用于测试目的。
3. org.springframework.security.core.userdetail.User– 这是大多数自定义应用程序中使用的东西。您可以在自己的用户对象的自定义实现上扩展此User类。
现在,根据上面的内容,如果有任何请求需要进行身份验证,则由于我们已经准备好了UserDetailsService,我们将从由UserDetailsService返回的UserDetails对象中获取发送请求的用户并使用来自我们的UserDetailsService的用户名/密码验证其发送的用户名/密码。
通过这种方式,用户得到了认证。
注意: 从用户接收到的密码会自动哈希处理。因此,如果我们没有来自UserDetailsService的密码哈希表示形式,即使密码正确,也会失败。
为了防止这种情况,我们向容器提供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();
}
身份验证提供程序主要由一个方法组成,一个基本的实现可能如下所示:
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允许等超出了范围。