我对Spring Boot相对较新,并且一直在探索它的身份验证功能.我注意到Spring Boot提供了方便的开箱即用身份验证,前提是用户名和用户_roles表中提供了用户名和角色.然而,我面临着跨两种不同用户类型实现身份验证的挑战,这两种用户类型必须分开并存储在不同的表上,因为它们在我的Spring Boot应用程序中保存了不同类型的数据SQL表.

这是这两个表的收件箱(我知道这些表中没有密码字段或用户名字段,可以稍后添加,我只是想要一个更大的解决方案.)这是乘客表,这是常规身份验证用户.

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,并为这些表定义了实体.因此,我们拥有扩展JPARepository接口的存储库.有人能否提供指导或潜在的解决方案来在此上下文中实现跨越多个SQL表的身份验证?任何例子或指针都将不胜感激.

提前感谢!

推荐答案

在现实世界中,不同的业务用户很可能需要从不同的来源进行身份验证.让我们来看看解决方案:

您可以 for each 入口注册两个不同的SecurityFilterChain.我建议你了解Spring Security Architecture.FilterChainProxy将根据securityMatcher决定使用哪个SecurityFilterChain.

同样,您可以 for each 收件箱创建两个不同的UserDetailsService.

SecurityConfiguration

@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 will decide which securityFilterChain will be used
            .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();
 }
}

PassengerUserDetailsService

@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();
 }
}

EmployeeUserDetailsService

@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();
 }
}

我嘲笑了每UserDetailsService个用户,您可以从各自的收件箱中获取这些用户.您可以参考org.springframework.security.authentication.dao.DaoAuthenticationProvider班以供参考.

您不需要遵循Spring的默认ddl来定义您的模式.您始终可以使用现有的模式并使用UserDetailsService使用自定义查询.提供它们可以快速启动您的项目,无需手动配置.

Java相关问答推荐

Java SSLocket查明客户端是否发送了证书

如何用javac编译Java类,即使对像java.lang.*这样的基本类也没有任何依赖关系?

Annotation @ Memphier无法正常工作,并表示:class需要一个bean,但找到了2个bean:

H2弹簧靴试验跌落台

滚动视图&不能在alert 对话框中工作(&Q;&Q;)

按属性值从流中筛选出重复项

如何在Java记录中设置BigDecimal类型属性的精度?

如何让JVM在SIGSEGV崩溃后快速退出?

Java.lang.invke.LambdaConversionException:实例方法InvokeVirtual的参数数量不正确

在使用具有不同成本的谓词调用allMatch之前对Java流进行排序会带来什么好处吗?

如果执行@BeForeEach#repository.save(),则测试中的UnitTest最终UUID会发生更改

Java KeyListener不工作或被添加

如何使用Java对随机生成的字母数字优惠券代码进行过期设置

TinyDB问题,无法解析符号';上下文&

在Spring Boot中使用咖啡因进行缓存

rest api服务 spring 启动中出现IllegalFormatConversionException

如何在MPAndroidChart中的条形图上正确添加标签

ControlsFX RangeSlider在方向垂直时滞后

java 11上出现DateTimeParseException,但java 8上没有

如何转换Vector<;对象>;转换为int?