目标:
- 学习自定义Authentication Provider(认证程序)
本章还是基于以下图片进行学习:
如图所示,AuthenticationProvider负责身份验证逻辑。AuthenticationManager接收来自HTTP过滤层的请求,并将认证用户的职责委托给AuthenticationProvider。
- 如果发起请求的用户没有经过身份验证,则返回给客户端的响应状态为HTTP 401 Unauthorized。
- 如果用户通过了身份验证,则用户详细信息以及身份验证状态将存储在SecurityContext中。
0.AuthenticationManager接口简介
AuthenticationManager是负责管理认证流程的核心接口。默认的实现类是ProviderManager,主要负责管理多个AuthenticationProvider,并委托它们进行用户身份认证。ProviderManager会遍历所有支持的AuthenticationProvider,找到第一个能够成功认证的Provider。如果某个Provider认证成功,则整个认证过程结束;如果所有Provider都认证失败,则会尝试使用双亲认证管理器(如果有设置)继续认证。在认证过程中,如果某个AuthenticationProvider抛出AuthenticationException,整个认证过程不会停止,会继续尝试下一个Provider;如果抛出AccountStatusException或InternalAuthenticationServiceException,则异常会被继续抛出,整个认证过程停止;如果抛出其他异常,则整个认证过程停止。
当用户登录时,AuthenticationManager会将认证请求传递给各个AuthenticationProvider进行处理。每个AuthenticationProvider根据其实现逻辑(如数据库查询、LDAP验证等)来验证用户的身份。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
1.AuthenticationProvider接口简介
AuthenticationProvider是一个接口,用于执行具体的认证逻辑。它接收一个Authentication对象(如用户名和密码),并尝试认证用户。每个AuthenticationProvider负责某种类型的认证方式,例如基于用户名和密码的认证、JWT认证等。如果某个AuthenticationProvider成功验证了用户身份,它会返回一个已认证的Authentication对象;否则,会抛出异常表示认证失败。
默认情况下,spring security将匹配请求中提供的用户名和密码,以验证用户身份。用户详细信息通常从提供的UserDetailsService获取,UserDetailsService从底层数据库加载用户详细信息。
但是,当我们不得不使用第三方提供的身份信息认证用户或某些spring security不支持的特定系统时,我们需要使用自定义AuthenticationProvider实现创建自定义身份认证逻辑。
以下是接口的源代码:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
- authenticate()方法接收一个未经身份认证的
Authentication
对象作为参数,并返回一个经过完整身份认证的Authentication
对象,该对象具有包括凭据在内的最终身份认证结果。 - 如果AuthenticationProvider无法支持所传递的Authentication 对象的身份认证,它可以返回null。在这种情况下,如果应用程序有多个AuthorizationProvider类,那么将尝试下一个Provider。
- 如果身份验证失败,它应该抛出AuthenticationException。
- 如果此AuthenticationProvider支持指定的Authentication 对象,则返回true。
- 在有多个AuthenticationProvider实现类的情况下,能够执行身份认证的AuthenticationProvider的选择是由
ProviderManager
(一个AuthenticationManager接口的实现类)在运行时执行的。
2.实现自定义认证程序
创建和注册自定义认证程序需要两个步骤:
- 决定新的AuthenticationProvider支持哪些类型的Authentication 对象?
- 覆盖supports()方法来指定它。在本例中,我们使用UsernamePasswordAuthenticationToken类型,它支持基于用户名和密码的身份验证流。
- 通过覆盖authenticate()方法来实现身份验证逻辑。
- 在本例中,我们在isValidUser()方法中编写认证逻辑。
package com.drson.usermanagement.service;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
// 在这个函数中,我们需要连接身份提供者并验证用户
// 出于演示目的,我们对单个用户进行硬编码
UserDetails isValidUser(String username, String password) {
if (username.equalsIgnoreCase("drson")
&& password.equals("123456")) {
UserDetails user = User
.withUsername(username)
.password("NOT_DISCLOSED")
.roles("USER_ROLE")
.build();
return user;
}
return null;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = isValidUser(username, password);//不再是从数据库加载用户信息
if (userDetails != null) {
return new UsernamePasswordAuthenticationToken(
username,
password,
userDetails.getAuthorities());
} else {
throw new BadCredentialsException("Incorrect user credentials !!");
}
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
3.注册自定义认证程序
package com.drson.usermanagement.config;
import com.drson.usermanagement.service.CustomAuthenticationProvider;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
@Resource
private CustomAuthenticationProvider customAuthenticationProvider;
//定义密码编码器
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//注册新的认证程序
http.authenticationProvider(customAuthenticationProvider);
http.authorizeHttpRequests((authorize) ->{
authorize.requestMatchers("/login").permitAll();//登录API不需要认证
authorize.anyRequest().authenticated();//其它所有api都需要认证才能访问
});
http.formLogin(Customizer.withDefaults());//使用默认登录表单
return http.build();
}
}
SecurityFilterChain的工作原理
当请求到达FilterChainProxy时,它会根据当前请求匹配得到对应的SecurityFilterChain,然后将请求依次转发给该SecurityFilterChain中的所有Security Filters。每个过滤器根据其职责对请求进行处理,最终决定是否允许该请求继续处理或返回响应。
4.小结
本章简单实现了自定义的认证程序,了解了AuthenticationProvider接口的简单用法,以及简单配置了SecurityFilterChain 。