Что такое Spring Security
Spring Security — это фреймворк для аутентификации, авторизации и защиты приложений. Он обеспечивает защиту от атак (CSRF, XSS, SQL Injection) и поддерживает различные протоколы (JWT, OAuth2, SAML).
Архитектура
▸Фильтры Spring Security
Spring Security работает через цепочку фильтров (Filter Chain):
SecurityContextPersistenceFilter — восстанавливает SecurityContext
UsernamePasswordAuthenticationFilter — обрабатывает форму логина
BasicAuthenticationFilter — Basic HTTP Auth
ExceptionTranslationFilter — обрабатывает исключения
FilterSecurityInterceptor — проверяет авторизацию
Базовая конфигурация
1@Configuration2@EnableWebSecurity3@RequiredArgsConstructor4public class SecurityConfig {56 private final JwtAuthenticationFilter jwtAuthFilter;7 private final AuthenticationProvider authenticationProvider;89 @Bean10 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {11 http12 .csrf(csrf -> csrf.disable())13 .authorizeHttpRequests(auth -> auth14 .requestMatchers("/api/auth/**").permitAll()15 .requestMatchers("/api/public/**").permitAll()16 .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()17 .requestMatchers("/api/admin/**").hasRole("ADMIN")18 .anyRequest().authenticated()19 )20 .sessionManagement(session -> session21 .sessionCreationPolicy(SessionCreationPolicy.STATELESS)22 )23 .authenticationProvider(authenticationProvider)24 .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);2526 return http.build();27 }2829 @Bean30 public PasswordEncoder passwordEncoder() {31 return new BCryptPasswordEncoder();32 }33}
JWT-аутентификация
▸Генерация токена
1@Service2public class JwtService {34 @Value("${jwt.secret}")5 private String secretKey;67 @Value("${jwt.expiration}")8 private long jwtExpiration;910 public String generateToken(UserDetails userDetails) {11 return Jwts.builder()12 .setSubject(userDetails.getUsername())13 .setIssuedAt(new Date())14 .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))15 .signWith(getSigningKey(), SignatureAlgorithm.HS256)16 .compact();17 }1819 private Key getSigningKey() {20 byte[] keyBytes = Decoders.BASE64.decode(secretKey);21 return Keys.hmacShaKeyFor(keyBytes);22 }23}
▸JWT фильтр
1@Component2@RequiredArgsConstructor3public class JwtAuthenticationFilter extends OncePerRequestFilter {45 private final JwtService jwtService;6 private final UserDetailsService userDetailsService;78 @Override9 protected void doFilterInternal(10 HttpServletRequest request,11 HttpServletResponse response,12 FilterChain filterChain) throws ServletException, IOException {1314 final String authHeader = request.getHeader("Authorization");15 final String jwt;16 final String userEmail;1718 if (authHeader == null || !authHeader.startsWith("Bearer ")) {19 filterChain.doFilter(request, response);20 return;21 }2223 jwt = authHeader.substring(7);24 userEmail = jwtService.extractUsername(jwt);2526 if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {27 UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);2829 if (jwtService.isTokenValid(jwt, userDetails)) {30 UsernamePasswordAuthenticationToken authToken =31 new UsernamePasswordAuthenticationToken(32 userDetails, null, userDetails.getAuthorities());33 authToken.setDetails(34 new WebAuthenticationDetailsSource().buildDetails(request));35 SecurityContextHolder.getContext().setAuthentication(authToken);36 }37 }38 filterChain.doFilter(request, response);39 }40}
Ролевая модель
▸Authorities и Roles
1@Service2@RequiredArgsConstructor3public class UserService implements UserDetailsService {45 private final UserRepository userRepository;67 @Override8 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {9 User user = userRepository.findByEmail(username)10 .orElseThrow(() -> new UsernameNotFoundException("User not found"));1112 return org.springframework.security.core.userdetails.User.builder()13 .username(user.getEmail())14 .password(user.getPassword())15 .roles(user.getRoles().stream()16 .map(Role::name)17 .toArray(String[]::new))18 .build();19 }20}
▸Проверка авторизации в коде
1@Service2@RequiredArgsConstructor3public class DocumentService {45 private final SecurityService securityService;67 public Document getDocument(Long id) {8 Document doc = documentRepository.findById(id)9 .orElseThrow(() -> new RuntimeException("Not found"));1011 // Проверка владения12 if (!doc.getOwnerId().equals(securityService.getCurrentUserId())13 && !securityService.hasRole("ADMIN")) {14 throw new AccessDeniedException("Not authorized");15 }1617 return doc;18 }19}
Обработка ошибок аутентификации
1@RestControllerAdvice2public class SecurityExceptionHandler {34 @ExceptionHandler(AccessDeniedException.class)5 public ResponseEntity<ErrorResponse> handleAccessDenied(AccessDeniedException ex) {6 return ResponseEntity.status(HttpStatus.FORBIDDEN)7 .body(new ErrorResponse("ACCESS_DENIED", "Недостаточно прав"));8 }910 @ExceptionHandler(BadCredentialsException.class)11 public ResponseEntity<ErrorResponse> handleBadCredentials(BadCredentialsException ex) {12 return ResponseEntity.status(HttpStatus.UNAUTHORIZED)13 .body(new ErrorResponse("INVALID_CREDENTIALS", "Неверные учётные данные"));14 }15}
OAuth2 и Social Login
▸Google OAuth2
1@Configuration2public class OAuth2Config {34 @Bean5 public ClientRegistrationRepository clientRegistrationRepository() {6 ClientRegistration google = ClientRegistration.withRegistrationId("google")7 .clientId("your-client-id")8 .clientSecret("your-client-secret")9 .scope("openid", "profile", "email")10 .authorizationUri("https://accounts.google.com/o/oauth2/v2/auth")11 .tokenUri("https://oauth2.googleapis.com/token")12 .userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")13 .userNameAttributeName(IdTokenClaimNames.SUB)14 .clientName("Google")15 .build();1617 return new InMemoryClientRegistrationRepository(google);18 }19}
Тестирование безопасности
1@SpringBootTest2@AutoConfigureMockMvc3class SecurityTest {45 @Autowired6 private MockMvc mockMvc;78 @Test9 void shouldReturn401_whenNoToken() throws Exception {10 mockMvc.perform(get("/api/users"))11 .andExpect(status().isUnauthorized());12 }1314 @Test15 @WithMockUser(roles = "USER")16 void shouldReturn200_whenAuthenticated() throws Exception {17 mockMvc.perform(get("/api/users"))18 .andExpect(status().isOk());19 }2021 @Test22 @WithMockUser(roles = "USER")23 void shouldReturn403_whenAdminEndpoint() throws Exception {24 mockMvc.perform(get("/api/admin/users"))25 .andExpect(status().isForbidden());26 }27}
Заключение
Spring Security — критически важная тема для Java-разработчика. На собеседовании спрашивают про JWT, авторизацию по ролям, защиту эндпоинтов, OAuth2 и типичные ошибки безопасности. Практикуйтесь с настройкой безопасности в реальных проектах.