mcpField
J
java-security

java-security

github.com/claude-dev-suiteセキュリティ4651 回インストール2 日前に更新
リポジトリ

java-security について

java-securityは、Javaアプリケーションのセキュリティ対策を支援する開発者向けクイックリファレンススキルです。Spring Securityを中心に、依存関係の監査やWeb脆弱性対策、認証、入力検証を扱います。

主な機能

  1. 01MavenとGradleの依存関係監査
  2. 02Spring Securityの設定例
  3. 03SQLインジェクションとXSS対策
  4. 04認証、入力検証、ファイルアップロード
  5. 05セキュリティイベント記録とトラブル対応

活用シーン

  1. 01MavenまたはGradleの依存リスク確認
  2. 02Spring Bootのセキュリティフィルター設定
  3. 03SQLインジェクションとクロスサイトスクリプティングの回避
  4. 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!
}

アンチパターン

アンチパターン問題点正しい方法
文字列連結を使った @QuerySQL インジェクション名前付きパラメーター :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

関連スキル

コメント