我对 Spring Boot 比较陌生,我一直在探索它的身份验证功能。我注意到 Spring Boot 提供了方便的开箱即用身份验证,前提是用户名和角色在 users 和 users_roles 表中提供。然而,我在跨两种不同的用户类型实现身份验证时面临着挑战,这两种用户类型必须分开并存储在不同的表中,因为它们在我的 Spring Boot 应用程序中保存不同类型的数据 SQL 表。
这是这两个表的 DDL(我知道这些表中没有密码字段或用户名字段,它们可以稍后添加,我只是想要一个更大的解决方案。)这是乘客表,这意味着常规的经过身份验证的用户.
CREATE TABLE IF NOT EXISTS passengers (
passenger_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
passport_number VARCHAR(15) UNIQUE NOT NULL,
nationality VARCHAR(255) NOT NULL,
contact_details VARCHAR(255)
);
另一张是员工表,它具有管理员权限。
CREATE TABLE IF NOT EXISTS employees (
employee_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
role VARCHAR(100) NOT NULL,
contact_info VARCHAR(255),
airport_id INT,
FOREIGN KEY (airport_id) REFERENCES airports(airport_id)
);
这是我出于演示目的而编写的一个基本示例(这不适用于我在项目中需要的角色)。所以这里我确实需要从两个不同的表中获取不同的用户。我不知道这样的场景的最佳实践是什么。
@Configuration
public class DemoSecurityConfig {
// add support for JDBC ... no more hardcoded users :-)
@Bean
public UserDetailsManager userDetailsManager(DataSource dataSource) {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
// define query to retrieve a user by username
jdbcUserDetailsManager.setUsersByUsernameQuery(
"select user_id, pw, active from members where user_id=?");
// define query to retrieve the authorities/roles by username
jdbcUserDetailsManager.setAuthoritiesByUsernameQuery(
"select user_id, role from roles where user_id=?");
return jdbcUserDetailsManager;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(configurer ->
configurer
.requestMatchers(HttpMethod.GET, "/api/employees").hasRole("EMPLOYEE")
.requestMatchers(HttpMethod.GET, "/api/employees/**").hasRole("EMPLOYEE")
.requestMatchers(HttpMethod.POST, "/api/employees").hasRole("MANAGER")
.requestMatchers(HttpMethod.PUT, "/api/employees").hasRole("MANAGER")
.requestMatchers(HttpMethod.DELETE, "/api/employees/**").hasRole("ADMIN")
);
// use HTTP Basic authentication
http.httpBasic(Customizer.withDefaults());
// disable Cross Site Request Forgery (CSRF)
// in general, not required for stateless REST APIs that use POST, PUT, DELETE and/or PATCH
http.csrf(csrf -> csrf.disable());
return http.build();
}
}
我一直在考虑仅维护另一个用户表和两个表的 user_roles 表中的必填字段。但后来我想我会遇到另一个问题,我需要一种类型的用户的数据,并且必须从一个或另一个表中获取。
我们正在使用 Spring Data JPA 并为这些表定义了实体。所以我们有扩展 JPARepositoryInterface 的存储库。有人可以提供指导或潜在的解决方案来实现在此上下文中跨多个 SQL 表的身份验证吗?任何示例或指示将不胜感激。
提前谢谢您!
在现实世界中,不同的业务用户很可能需要从不同的来源进行身份验证。让我们来看看解决方案:
您可以为每个入口点注册两个不同的
SecurityFilterChain
。我建议你了解Spring Security Architecture
同样,您可以为每个数据源创建两个不同的
UserDetailsService
。
安全配置
@Configuration
public class SecurityConfiguration {
private final PassengerUserDetailsService passengerUserDetailsService;
private final EmployeeUserDetailsService employeeUserDetailsService;
private final PasswordEncoder passwordEncoder;
public SecurityConfiguration(PassengerUserDetailsService passengerUserDetailsService,
EmployeeUserDetailsService employeeUserDetailsService,
PasswordEncoder passwordEncoder) {
this.passengerUserDetailsService = passengerUserDetailsService;
this.employeeUserDetailsService = employeeUserDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Bean
@Order(0)
public SecurityFilterChain filterChain0(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.securityMatcher("/passenger/**") // this wi
.authorizeHttpRequests(
auth -> auth.
requestMatchers(HttpMethod.GET, "/passenger/**").hasRole("PASSENGER")
)
.httpBasic(Customizer.withDefaults())
.csrf(CsrfConfigurer::disable)
.userDetailsService(passengerUserDetailsService);
return httpSecurity.build();
}
@Bean
@Order(1)
public SecurityFilterChain filterChain1(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.securityMatcher("/employee/**")
.authorizeHttpRequests(
auth -> auth.
requestMatchers(HttpMethod.GET, "/employee/**").hasRole("EMPLOYEE")
)
.httpBasic(Customizer.withDefaults())
.csrf(CsrfConfigurer::disable)
.userDetailsService(employeeUserDetailsService);
return httpSecurity.build();
}
}
旅客用户详情服务
@Service
public class PassengerUserDetailsService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
PassengerUserDetailsService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User.UserBuilder builder = User.builder();
builder.username(username);
builder.password(passwordEncoder.encode(username));
switch (username) {
case "passenger":
builder.roles("PASSENGER");
break;
default:
throw new UsernameNotFoundException("User not found.");
}
return builder.build();
}
}
员工用户详细信息服务
@Service
public class EmployeeUserDetailsService implements UserDetailsService {
private final PasswordEncoder passwordEncoder;
EmployeeUserDetailsService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User.UserBuilder builder = User.builder();
builder.username(username);
builder.password(passwordEncoder.encode(username));
switch (username) {
case "employee":
builder.roles("EMPLOYEE");
break;
default:
throw new UsernameNotFoundException("User not found.");
}
return builder.build();
}
}