Как добавить кастомную логику аутентификации или авторизации в Spring Security?

Ответ

Кастомизация в Spring Security обычно реализуется через расширение ключевых компонентов.

1. Кастомная аутентификация (например, по JWT):

  • Создайте фильтр для извлечения и валидации токена.
  • Реализуйте AuthenticationProvider или используйте стандартный, установив кастомный UserDetailsService.
@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        // Ваша логика загрузки пользователя из БД или другого источника
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        // Преобразование ролей/прав в GrantedAuthority
        List<GrantedAuthority> authorities = user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
                .collect(Collectors.toList());
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                authorities
        );
    }
}

2. Кастомная конфигурация доступа: Настройте SecurityFilterChain, чтобы определить правила для разных эндпоинтов и подключить свои фильтры.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authz -> authz
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
            .requestMatchers("/public/**").permitAll()
            .anyRequest().authenticated()
        )
        // Добавление кастомного JWT-фильтра перед стандартным
        .addFilterBefore(new JwtAuthenticationFilter(), 
                         UsernamePasswordAuthenticationFilter.class)
        .csrf(csrf -> csrf.disable()) // Отключаем для stateless API
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
    return http.build();
}

3. Кастомная логика авторизации: Для сложных правил (например, на основе атрибутов объекта) реализуйте @PreAuthorize с SpEL или создайте собственный PermissionEvaluator.

Ответ 18+ 🔞

А, ну вот, опять эти ваши кастомные штуки в Spring Security. Слушай, а как же без них, блядь? Без кастомизации там нихуя не работает, как надо. Все эти стандартные формы логина — они ж для школьных проектов, а в реальной жизни нужно всё под себя запиливать, иначе писец.

Так, смотри, первое, что обычно всем нужно — это своя аутентификация. Ну, например, по JWT, который сейчас у всех в рот засунут. Что делаем? Правильно, пишем свой фильтр, который этот токен из запроса выковыривает, проверяет, не просрочился ли, не подделан ли. А потом, блядь, нужно как-то сказать Спрингу: «Смотри, чувак, вот пользователь, вот его роли, считай его своим».

Для этого либо свой AuthenticationProvider городишь, либо, что проще — используешь стандартный, но подсовываешь ему свой UserDetailsService. Вот смотри, как это выглядит, если не выёбываться:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        // Твоя логика, где ты лезешь в базу или куда-то ещё за пользователем
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        // Превращаешь его роли в эти ваши GrantedAuthority
        List<GrantedAuthority> authorities = user.getRoles().stream()
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
                .collect(Collectors.toList());
        // И возвращаешь обёртку, которую Спринг уже понимает
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                authorities
        );
    }
}

Всё, блядь. Теперь твой сервис знает, где брать юзеров. Но это полдела.

Дальше — конфигурация доступа. Это где ты говоришь, кому куда можно ходить. Тут уже в SecurityFilterChain всё описываешь. И, что самое главное, свой JWT-фильтр туда впихиваешь, чтобы он работал до стандартной аутентификации по логину-паролю.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authz -> authz
            .requestMatchers("/api/admin/**").hasRole("ADMIN") // Сюда только админы
            .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN") // Сюда юзеры и админы
            .requestMatchers("/public/**").permitAll() // А сюда вообще все, кто угодно
            .anyRequest().authenticated() // Всё остальное — только для авторизованных
        )
        // Вот тут магия! Подсовываем свой фильтр перед стандартным
        .addFilterBefore(new JwtAuthenticationFilter(),
                         UsernamePasswordAuthenticationFilter.class)
        .csrf(csrf -> csrf.disable()) // Для API обычно выключаем, иначе мозг сломаешь
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // Без сессий, stateless же
    return http.build();
}

Ну и третий пункт — это когда простых ролей уже не хватает. Ну там, «пользователь может редактировать только СВОЮ статью», а не все подряд. Вот тут уже начинается цирк с @PreAuthorize и SpEL выражениями. А если совсем пиздец сложная логика, то пишешь свой PermissionEvaluator — это такая штука, которая решает, можно ли юзеру делать что-то с конкретным объектом. Но это, блядь, уже для гурманов, когда простые методы не катят.

Короче, суть в чём: Spring Security — он как конструктор. Детали стандартные есть, но если хочешь что-то своё — бери и переопределяй. Главное, не накосячить в цепочке фильтров, а то будет «доступ запрещён» там, где не надо, и наоборот.