RU | EN | DE

1. Spring Security Basics

Spring Security = фильтры вокруг приложения, которые решают:

  • кто может войти (Authentication),
  • что он может делать (Authorization).

📌 Подключение:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

⚡ После добавления этой зависимости:

  • Spring автоматически защищает все эндпоинты.
  • По умолчанию включается Basic Auth → логин = user, пароль генерируется при старте (в логах).

2. Basic Auth

📄 Настройка:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain security(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            .httpBasic(); // Basic Auth
        return http.build();
    }
}

Теперь:

  • GET /api/public/** → доступен всем
  • остальные → требуют Basic Auth.

3. Form Login

http.formLogin(withDefaults());

При попытке входа пользователь будет видеть стандартную HTML-форму.

4. UserDetailsService (свои пользователи)

@Service
public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        return User.withUsername(username)
                   .password("{noop}password") // {noop} = без шифрования
                   .roles("USER")
                   .build();
    }
}

5. Password Encoding

Никогда не храни пароли в plain text.

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
String encoded = passwordEncoder.encode("secret");
System.out.println(encoded);
// $2a$10$F5n2k...

6. JWT Authentication

📄 Зависимость:

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.11.5</version>
</dependency>
 
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.11.5</version>
  <scope>runtime</scope>
</dependency>
 
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId>
  <version>0.11.5</version>
  <scope>runtime</scope>
</dependency>

📄 Генерация токена:

String token = Jwts.builder()
    .setSubject("user1")
    .claim("role", "USER")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
    .signWith(SignatureAlgorithm.HS256, "secret-key")
    .compact();

📄 Фильтр для проверки токена:

@Component
public class JwtFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String authHeader = req.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String jwt = authHeader.substring(7);
            Claims claims = Jwts.parser()
                .setSigningKey("secret-key")
                .parseClaimsJws(jwt)
                .getBody();
            String username = claims.getSubject();
            UsernamePasswordAuthenticationToken auth =
                new UsernamePasswordAuthenticationToken(username, null, List.of());
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        chain.doFilter(req, res);
    }
}

📄 Подключение фильтра:

@Bean
public SecurityFilterChain security(HttpSecurity http, JwtFilter jwtFilter) throws Exception {
    return http
        .csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
        )
        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
        .build();
}

Теперь API принимает токен:

Authorization: Bearer <jwt>

7. Role-based Access Control

@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String admin() {
    return "Hello Admin!";
}

📌 Включить проверку аннотаций:

@EnableMethodSecurity
public class SecurityConfig {}

8. OAuth2, OpenID Connect

📄 Зависимость:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

📄 Настройка (пример для Google):

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: <id>
            client-secret: <secret>
            scope: profile,email

📌 Теперь пользователь может логиниться через Google.

9. API Key (простейшая защита)

@Component
public class ApiKeyFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String apiKey = req.getHeader("X-API-KEY");
        if (!"secret-key".equals(apiKey)) {
            res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }
        chain.doFilter(req, res);
    }
}

10. Best Practices

  • ✅ Никогда не храни пароли в plain text → только BCrypt.
  • ✅ Для REST API используй JWT, для корпоративных порталов → OAuth****2/OpenID.
  • ✅ Защищай API → по умолчанию все закрыты.
  • ✅ Логику авторизации (roles/permissions) выноси в аннотации @PreAuthorize.
  • ✅ Не выключай CSRF для веб-форм, но для REST API обычно CSRF отключают.
  • ✅ Разделяй auth эндпоинты (/api/auth/) от бизнес-логики (/api/).