Я относительно новичок в Spring Boot и изучаю его функции аутентификации. Я заметил, что Spring Boot предлагает удобную встроенную аутентификацию при условии, что имена пользователей и роли указаны в таблицах пользователей и пользователей_ролей. Однако я столкнулся с проблемой реализации аутентификации для двух разных типов пользователей, которые должны быть отдельными и храниться в разных таблицах, поскольку они содержат разные типы таблиц SQL данных в моем приложении Spring Boot.
Вот 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 в этом контексте? Будем очень признательны за любые примеры или указатели.
Заранее спасибо!
@YogendraR Я подумываю о реализации двух UserDetailServices, по одному для каждой таблицы, а затем, возможно, добавлю их в AuthenticationProvider. Логины и пароли я извлеку из таблиц (добавлю в таблицы необходимые поля).
Позвольте мне сказать по-другому: пользователь пытается получить доступ к вашему приложению. Как вы определите, какую таблицу вам нужно использовать? Будете ли вы выполнять поиск в обеих таблицах, если этот конкретный пользователь существует? или у вас будут отдельные маршруты API для различий. типы пользователей? Я не спрашиваю вас о технических вопросах, а пытаюсь понять ваши бизнес-требования.
@YogendraR У меня будет две отдельные таблицы, поэтому они будут аутентифицироваться отдельно, поэтому для обеих будут разные конечные точки. Таким образом, пассажиру будет показан другой логин, а сотруднику — другой.
У вас должна быть одна таблица пользователей с ролями пользователей для аутентификации. Для бизнес-логики, которая зависит от роли пользователя, вы можете использовать таблицы сотрудников и пассажиров (вам просто нужно поле user_id в каждой из них).
у вас есть два способа сделать это: либо вы реализуете интерфейс UserDetailsService, попытаетесь получить пользователя из первой базы данных, если это не удастся, перейдите ко второй. Другой вариант — иметь две отдельные конечные точки, что означает два отдельных входа в систему, что, в свою очередь, означает две отдельные цепочки фильтров и две отдельные конфигурации безопасности. Я бы категорически не стал реализовывать собственный AuthenticationProvider, поскольку стандартный поставщик включает в себя дополнительные функции безопасности, такие как предотвращение атак с фиксацией сеанса и т. д. Не создавайте свой собственный, если вы действительно не уверены, что знаете, как работает безопасность.
или вы можете оптимизировать все до одного запроса и проверять пользователя в обеих таблицах, используя один запрос и оператор UNION.
@Asem, спасибо за это предложение, это действительно хорошая идея реализации. Я это очень ценю.
@Toerktumlare Спасибо за предоставление множества идей для этих решений, эти идеи очень осуществимы. Я очень благодарен.




В реальном мире вполне возможно, что разные бизнес-пользователи должны проходить аутентификацию из разных источников. Давайте рассмотрим решение:
Для каждой точки входа можно зарегистрировать два разных SecurityFilterChain. Предлагаю вам разобраться в Spring Security Architecture. FilterChainProxy решит, какой SecurityFilterChain будет использоваться на основе securityMatcher.
Аналогичным образом вы можете создать два разных 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 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();
}
}
ПассажирПользовательПодробностиСервис
@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();
}
}
В каждом UserDetailsService я высмеивал пользователей, которых вы можете получить из соответствующего источника данных. Вы можете обратиться к классу org.springframework.security.authentication.dao.DaoAuthenticationProvider для справки.
Вам не нужно следовать стандартному ddl Spring для определения вашей схемы. Вы всегда можете использовать существующую схему и использовать собственные запросы, используя UserDetailsService. Они предназначены для быстрого запуска вашего проекта без ручной настройки.
Я рад, что мой ответ оказался полезным. Еще одна вещь: вам не нужно следовать сценариям Spring по умолчанию для определения вашей схемы. Вы всегда можете использовать существующую схему и использовать собственные запросы, используя UserDetailsService
Ты можешь это сделать. Но скажите мне, как определить, какую аутентификацию использовать? будут ли у вас отдельные маршруты API?