From ecc
Java Spring Boot 서비스의 인증/권한 부여, 유효성 검사, CSRF, 비밀 정보(secrets), 헤더, 속도 제한 및 의존성 보안을 위한 Spring Security 모범 사례입니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
인증 기능을 추가하거나, 입력을 처리하거나, 엔드포인트를 생성하거나, 비밀 정보를 다룰 때 사용하세요.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
인증 기능을 추가하거나, 입력을 처리하거나, 엔드포인트를 생성하거나, 비밀 정보를 다룰 때 사용하세요.
httpOnly, Secure, SameSite=Strict 쿠키를 사용합니다.OncePerRequestFilter 또는 리소스 서버를 사용하여 토큰 유효성을 검사합니다.@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
public JwtAuthFilter(JwtService jwtService) {
this.jwtService = jwtService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
Authentication auth = jwtService.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
}
@EnableMethodSecurity@PreAuthorize("hasRole('ADMIN')") 또는 @PreAuthorize("@authz.canEdit(#id)") 사용@RestController
@RequestMapping("/api/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/users")
public List<UserDto> listUsers() {
return userService.findAll();
}
@PreAuthorize("@authz.isOwner(#id, authentication)")
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
@Valid와 함께 Bean Validation 사용@NotBlank, @Email, @Size, 커스텀 검증기// 나쁜 예: 유효성 검사 없음
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) {
return userService.create(dto);
}
// 좋은 예: 유효성이 검사된 DTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@NotNull @Min(0) @Max(150) Integer age
) {}
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserDto dto) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(userService.create(dto));
}
:param 바인딩을 사용하고, 절대 문자열을 결합하지 마세요.// 나쁜 예: 네이티브 쿼리에서 문자열 결합
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// 좋은 예: 파라미터화된 네이티브 쿼리
@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
List<User> findByName(@Param("name") String name);
// 좋은 예: Spring Data 파생 쿼리 (자동 파라미터화)
List<User> findByEmailAndActiveTrue(String email);
PasswordEncoder 빈을 사용하세요.@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 강도 계수 12
}
// 서비스에서
public User register(CreateUserDto dto) {
String hashedPassword = passwordEncoder.encode(dto.password());
return userRepository.save(new User(dto.email(), hashedPassword));
}
http
.csrf(csrf -> csrf.disable())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
application.yml에 자격 증명을 직접 적지 말고 플레이스홀더를 사용하세요.# 나쁜 예: application.yml에 하드코딩
spring:
datasource:
password: mySecretPassword123
# 좋은 예: 환경 변수 플레이스홀더
spring:
datasource:
password: ${DB_PASSWORD}
# 좋은 예: Spring Cloud Vault 통합
spring:
cloud:
vault:
uri: https://vault.example.com
token: ${VAULT_TOKEN}
http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'"))
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
.xssProtection(Customizer.withDefaults())
.referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));
*를 사용하지 마세요.@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
// SecurityFilterChain에서:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
// 엔드포인트별 속도 제한을 위해 Bucket4j 사용
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
private Bucket createBucket() {
return Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
.build();
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String clientIp = request.getRemoteAddr();
Bucket bucket = buckets.computeIfAbsent(clientIp, k -> createBucket());
if (bucket.tryConsume(1)) {
chain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
}
}
}
기억하세요: 기본적으로 거부하고(Deny by default), 입력을 검증하며, 최소 권한 원칙을 따르고, 설정에 의한 보안을 우선시하세요.