java-security について
java-securityは、Javaアプリケーションのセキュリティ対策を支援する開発者向けクイックリファレンススキルです。Spring Securityを中心に、依存関係の監査やWeb脆弱性対策、認証、入力検証を扱います。
主な機能
- 01MavenとGradleの依存関係監査
- 02Spring Securityの設定例
- 03SQLインジェクションとXSS対策
- 04認証、入力検証、ファイルアップロード
- 05セキュリティイベント記録とトラブル対応
活用シーン
- 01MavenまたはGradleの依存リスク確認
- 02Spring Bootのセキュリティフィルター設定
- 03SQLインジェクションとクロスサイトスクリプティングの回避
- 04JWT、ファイルアップロード、アクセス規則の確認
MCP 対応
READMEでは、mcp__documentation__fetch_docsによるSpring Bootドキュメント取得に言及しています。このスキルがMCPサーバーまたはクライアント機能を提供するとは説明されていません。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill java-securityインストール後、Claude Code で/java-securityを入力して読み込まれたか確認します。
詳細
Java セキュリティ - クイックリファレンス
このスキルを使用しない場合
- 一般的な OWASP の概念 -
owaspまたはowasp-top-10スキルを使用します - Node.js/TypeScript のセキュリティ - 基本のセキュリティスキルを使用します
- Python のセキュリティ -
python-securityスキルを使用します - シークレット管理 -
secrets-managementスキルを使用します
詳細な知識:Spring Security のドキュメントには、テクノロジーとして
spring-bootを指定してmcp__documentation__fetch_docsを使用します。
依存関係の監査
# Maven - OWASP Dependency Check
mvn dependency-check:check
# Maven - check for updates
mvn versions:display-dependency-updates
# Gradle - dependency check plugin
./gradlew dependencyCheckAnalyze
# Snyk for Java
snyk test --all-projects
Maven プラグインの設定
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>9.0.9</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS>
<suppressionFile>dependency-check-suppression.xml</suppressionFile>
</configuration>
</plugin>
Spring Security の設定
基本的なセキュリティ設定(Spring Boot 3.x)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.headers(headers -> headers
.contentSecurityPolicy(csp ->
csp.policyDirectives("default-src 'self'; script-src 'self'"))
.frameOptions(frame -> frame.deny())
.xssProtection(xss -> xss.disable()) // Use CSP instead
.contentTypeOptions(Customizer.withDefaults())
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://myapp.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
}
パスワードのエンコーディング
@Bean
public PasswordEncoder passwordEncoder() {
// BCrypt with strength 12 (recommended)
return new BCryptPasswordEncoder(12);
}
// Usage
String encoded = passwordEncoder.encode(rawPassword);
boolean matches = passwordEncoder.matches(rawPassword, encoded);
メソッドレベルのセキュリティ
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {}
// Usage in service
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
@PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
public User getUser(Long userId) { ... }
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Document getDocument(Long id) { ... }
SQL インジェクションの防止
JPA/Hibernate - 安全
// SAFE - Named parameters
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);
// SAFE - Criteria API
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.where(cb.equal(root.get("email"), email));
// SAFE - Spring Data JPA method names
Optional<User> findByEmailAndStatus(String email, Status status);
JPA/Hibernate - 安全ではない
// UNSAFE - String concatenation
@Query("SELECT u FROM User u WHERE u.email = '" + email + "'") // NEVER!
// UNSAFE - Native query without parameters
@Query(value = "SELECT * FROM users WHERE email = " + email, nativeQuery = true) // NEVER!
JDBC Template - 安全
// SAFE - Parameterized query
jdbcTemplate.query(
"SELECT * FROM users WHERE email = ? AND status = ?",
new Object[]{email, status},
userRowMapper
);
// SAFE - Named parameters
namedParameterJdbcTemplate.query(
"SELECT * FROM users WHERE email = :email",
Map.of("email", email),
userRowMapper
);
XSS の防止
Thymeleaf(自動エスケープ)
<p th:text="${userInput}"></p>
<p th:utext="${userInput}"></p>
API レスポンスのサニタイズ
// Use OWASP Java HTML Sanitizer
import org.owasp.html.PolicyFactory;
import org.owasp.html.Sanitizers;
PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
String safeHtml = policy.sanitize(userInput);
認証のベストプラクティス
JWT の設定
@Component
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration:3600000}") // 1 hour
private long expiration;
public String generateToken(Authentication auth) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(auth.getName())
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(Keys.hmacShaKeyFor(secret.getBytes()), SignatureAlgorithm.HS512)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(secret.getBytes()))
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
Resilience4j によるレート制限
@RateLimiter(name = "loginRateLimiter", fallbackMethod = "loginFallback")
public AuthResponse login(LoginRequest request) {
// login logic
}
public AuthResponse loginFallback(LoginRequest request, RequestNotPermitted ex) {
throw new TooManyRequestsException("Too many login attempts. Try again later.");
}
# application.yml
resilience4j:
ratelimiter:
instances:
loginRateLimiter:
limitForPeriod: 5
limitRefreshPeriod: 15m
timeoutDuration: 0
入力値の検証
public record CreateUserRequest(
@NotBlank
@Email
@Size(max = 255)
String email,
@NotBlank
@Size(min = 12, max = 128)
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&]).*$",
message = "Password must contain uppercase, lowercase, number and special char")
String password,
@NotBlank
@Size(min = 2, max = 100)
@Pattern(regexp = "^[a-zA-Z\\s-']+$")
String name
) {}
@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody CreateUserRequest request) {
// request is already validated
}
ファイルアップロードのセキュリティ
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
// Validate file type
String contentType = file.getContentType();
if (!ALLOWED_TYPES.contains(contentType)) {
throw new InvalidFileTypeException("File type not allowed");
}
// Validate file size (also configure in application.yml)
if (file.getSize() > MAX_FILE_SIZE) {
throw new FileTooLargeException("File exceeds maximum size");
}
// Generate safe filename
String originalName = file.getOriginalFilename();
String safeName = UUID.randomUUID() + getExtension(originalName);
// Store outside web root
Path destination = uploadPath.resolve(safeName);
Files.copy(file.getInputStream(), destination);
return ResponseEntity.ok(safeName);
}
セキュリティイベントのログ記録
@Slf4j
@Component
public class SecurityEventLogger {
public void logLoginAttempt(String username, boolean success, HttpServletRequest request) {
log.info("Login attempt: user={}, success={}, ip={}, userAgent={}",
username,
success,
request.getRemoteAddr(),
request.getHeader("User-Agent")
);
}
public void logAccessDenied(String username, String resource, HttpServletRequest request) {
log.warn("Access denied: user={}, resource={}, ip={}",
username,
resource,
request.getRemoteAddr()
);
}
// NEVER log sensitive data
// log.info("Password: {}", password); // NEVER!
// log.info("Token: {}", jwt); // NEVER!
}
アンチパターン
| アンチパターン | 問題点 | 正しい方法 |
|---|---|---|
文字列連結を使った @Query | SQL インジェクション | 名前付きパラメーター :param を使用する |
ユーザーコンテンツに th:utext を使用する | XSS の脆弱性 | th:text を使用する(自動エスケープ) |
| パスワードに MD5/SHA1 を使用する | 簡単に解読される | 強度 12 以上の BCrypt を使用する |
| JWT シークレットをコード内に保存する | シークレットの漏えい | 環境変数を使用する |
機密性の高いエンドポイントに permitAll() を使用する | 不正アクセス | 明示的な認証ルールを定義する |
| ステートフルアプリで CSRF を無効にする | CSRF 攻撃 | セッションでは CSRF を有効にしておく |
Exception を捕捉して何もせずに処理する | セキュリティ上の問題が隠れる | ログに記録し、個別に処理する |
よくある問題のトラブルシューティング
| 問題 | 考えられる原因 | 解決策 |
|---|---|---|
| 有効なリクエストで 403 が返る | CSRF トークンがない | リクエストに CSRF トークンを含める |
| 有効な JWT で 401 が返る | トークンの期限切れ、またはキーが間違っている | 有効期限とシークレットキーを確認する |
| ブラウザーで CORS エラーが発生する | CORS の設定がない | allowedOrigins にオリジンを追加する |
| パスワード検証に失敗する | BCrypt のバージョンが一致しない | 同じエンコーダーのバージョンを使用する |
| メソッドセキュリティが機能しない | @EnableMethodSecurity がない | 設定クラスにアノテーションを追加する |
| 依存関係のチェックでビルドが失敗する | CVSS のしきい値が低すぎる | failBuildOnCVSS を調整するか、抑制する |
セキュリティスキャンコマンド
# OWASP Dependency Check
mvn dependency-check:check
./gradlew dependencyCheckAnalyze
# SpotBugs with Security Plugin
mvn spotbugs:check -Dspotbugs.plugins=com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0
# Snyk
snyk test --all-projects
# SonarQube (if configured)
mvn sonar:sonar -Dsonar.host.url=http://localhost:9000
関連スキル
コメント
Markdown 対応