Security Patterns trong Java

1. Authentication Patterns

1.1 JWT Authentication

@Configuration
public class JwtConfig {
    @Bean
    public JwtEncoder jwtEncoder() {
        JWK jwk = new RSAKey.Builder(publicKey)
            .privateKey(privateKey)
            .build();
        JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
        return new NimbusJwtEncoder(jwks);
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(publicKey).build();
    }
}

@Service
public class JwtTokenService {
    private final JwtEncoder encoder;

    public String generateToken(Authentication authentication) {
        Instant now = Instant.now();
        JwtClaimsSet claims = JwtClaimsSet.builder()
            .issuer("self")
            .issuedAt(now)
            .expiresAt(now.plus(1, ChronoUnit.HOURS))
            .subject(authentication.getName())
            .claim("scope", getScopes(authentication))
            .build();
        return encoder.encode(JwtEncoderParameters.from(claims))
            .getTokenValue();
    }
}

1.2 OAuth2/OpenID Connect

@Configuration
@EnableWebSecurity
public class OAuth2Config extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .authorizationEndpoint()
                    .baseUri("/oauth2/authorize")
                    .authorizationRequestRepository(cookieAuthorizationRequestRepository())
                .and()
                .redirectionEndpoint()
                    .baseUri("/oauth2/callback/*")
                .and()
                .userInfoEndpoint()
                    .userService(customOAuth2UserService)
                .and()
                .successHandler(oAuth2AuthenticationSuccessHandler)
                .failureHandler(oAuth2AuthenticationFailureHandler);
    }

    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
        DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
        return userRequest -> {
            OAuth2User oauth2User = delegate.loadUser(userRequest);
            return new CustomOAuth2User(oauth2User);
        };
    }
}

2. Authorization Patterns

2.1 Role-Based Access Control (RBAC)

@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true,
    securedEnabled = true,
    jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler = 
            new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new CustomPermissionEvaluator());
        return expressionHandler;
    }
}

@Service
public class UserService {
    @PreAuthorize("hasRole('ADMIN')")
    public User createUser(User user) {
        return userRepository.save(user);
    }

    @PostAuthorize("returnObject.username == authentication.principal.username or hasRole('ADMIN')")
    public User getUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException(id));
    }
}

2.2 Attribute-Based Access Control (ABAC)

@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(
            Authentication auth, 
            Object targetDomainObject,
            Object permission) {
        if ((auth == null) || (targetDomainObject == null)) {
            return false;
        }

        String targetType = targetDomainObject.getClass().getSimpleName();
        return hasPrivilege(auth, targetType, permission.toString());
    }

    private boolean hasPrivilege(
            Authentication auth, 
            String targetType, 
            String permission) {
        for (GrantedAuthority grantedAuth : auth.getAuthorities()) {
            if (grantedAuth.getAuthority()
                    .startsWith(targetType.toUpperCase())) {
                return true;
            }
        }
        return false;
    }
}

3. Encryption Patterns

3.1 Data-at-Rest Encryption

@Configuration
public class EncryptionConfig {
    @Bean
    public StringEncryptor stringEncryptor() {
        PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
        SimpleStringPBEConfig config = new SimpleStringPBEConfig();
        config.setPassword(encryptionKey);
        config.setAlgorithm("PBEWithHMACSHA512AndAES_256");
        config.setKeyObtentionIterations("1000");
        config.setPoolSize("1");
        config.setProviderName("SunJCE");
        config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
        config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
        config.setStringOutputType("base64");
        encryptor.setConfig(config);
        return encryptor;
    }
}

@Entity
public class SensitiveData {
    @Convert(converter = AttributeEncryptor.class)
    private String sensitiveInfo;
}

@Converter
public class AttributeEncryptor implements AttributeConverter<String, String> {
    private final StringEncryptor encryptor;

    @Override
    public String convertToDatabaseColumn(String attribute) {
        return encryptor.encrypt(attribute);
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        return encryptor.decrypt(dbData);
    }
}

3.2 Data-in-Transit Encryption

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .requiresChannel()
                .anyRequest()
                .requiresSecure()
            .and()
            .headers()
                .httpStrictTransportSecurity()
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000);
    }

    @Bean
    public TomcatServletWebServerFactory servletContainer() {
        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
        tomcat.addAdditionalTomcatConnectors(redirectConnector());
        return tomcat;
    }

    private Connector redirectConnector() {
        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
        connector.setScheme("http");
        connector.setPort(8080);
        connector.setSecure(false);
        connector.setRedirectPort(8443);
        return connector;
    }
}

4. Input Validation Patterns

4.1 Request Validation

@RestController
@Validated
public class UserController {
    @PostMapping("/users")
    public ResponseEntity<User> createUser(
            @Valid @RequestBody UserDTO userDTO) {
        return ResponseEntity.ok(userService.createUser(userDTO));
    }
}

public class UserDTO {
    @NotBlank
    @Size(min = 4, max = 50)
    private String username;

    @NotBlank
    @Email
    private String email;

    @NotBlank
    @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$")
    private String password;

    @AssertTrue(message = "Terms must be accepted")
    private boolean termsAccepted;
}

@ControllerAdvice
public class ValidationExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, List<String>>> handleValidationErrors(
            MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());
        return new ResponseEntity<>(getErrorsMap(errors), HttpStatus.BAD_REQUEST);
    }

    private Map<String, List<String>> getErrorsMap(List<String> errors) {
        Map<String, List<String>> errorResponse = new HashMap<>();
        errorResponse.put("errors", errors);
        return errorResponse;
    }
}

4.2 SQL Injection Prevention

@Repository
public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public User findByUsername(String username) {
        String sql = "SELECT * FROM users WHERE username = ?";
        return jdbcTemplate.queryForObject(
            sql,
            new Object[]{username},
            new BeanPropertyRowMapper<>(User.class)
        );
    }

    @Query("SELECT u FROM User u WHERE u.username = :username")
    public User findByUsernameJPA(@Param("username") String username);
}

5. Logging và Auditing

5.1 Security Event Logging

@Aspect
@Component
public class SecurityAuditAspect {
    private final AuditLogger auditLogger;

    @Around("@annotation(Audited)")
    public Object auditMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getName();
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        AuditEvent event = AuditEvent.builder()
            .principal(authentication.getName())
            .type("METHOD_ACCESS")
            .data(Map.of(
                "method", methodName,
                "class", className,
                "timestamp", Instant.now()
            ))
            .build();

        try {
            Object result = joinPoint.proceed();
            event.setOutcome("SUCCESS");
            return result;
        } catch (Exception e) {
            event.setOutcome("FAILURE");
            event.setError(e.getMessage());
            throw e;
        } finally {
            auditLogger.log(event);
        }
    }
}

5.2 Audit Trail

@Entity
@EntityListeners(AuditingEntityListener.class)
public class AuditableEntity {
    @CreatedBy
    private String createdBy;

    @CreatedDate
    private Instant createdDate;

    @LastModifiedBy
    private String lastModifiedBy;

    @LastModifiedDate
    private Instant lastModifiedDate;
}

@Configuration
@EnableJpaAuditing
public class AuditConfig {
    @Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.ofNullable(SecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .filter(Authentication::isAuthenticated)
            .map(Authentication::getName);
    }
}

6. Security Headers

6.1 HTTP Security Headers

@Configuration
public class SecurityHeadersConfig {
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return web -> web.ignoring()
            .requestMatchers("/resources/**");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .headers()
                .contentSecurityPolicy("default-src 'self'")
                .and()
                .xssProtection()
                .and()
                .frameOptions()
                    .deny()
                .and()
                .referrerPolicy(ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
                .and()
                .permissionsPolicy(permissions -> permissions
                    .policy("camera=(), microphone=(), geolocation=()"))
                .and()
                .contentTypeOptions()
                .and()
                .cacheControl();
        return http.build();
    }
}

6.2 CORS Configuration

@Configuration
public class CorsConfig {
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("https://trusted-domain.com"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
        configuration.setExposedHeaders(Arrays.asList("X-Custom-Header"));
        configuration.setAllowCredentials(true);
        configuration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

7. Best Practices

7.1 Security Guidelines

  • Principle of Least Privilege
  • Defense in Depth
  • Fail Securely
  • Complete Mediation
  • Separation of Duties
  • Keep Security Simple
  • Zero Trust Architecture
  • Security by Design

7.2 Implementation Guidelines

  • Use Strong Password Hashing
  • Implement Rate Limiting
  • Enable HTTPS Everywhere
  • Validate All Input
  • Implement Proper Session Management
  • Use Secure Dependencies
  • Regular Security Audits
  • Implement Proper Error Handling

8. References

  • OWASP Security Guidelines
  • Spring Security Reference
  • Java Security Coding Guidelines
  • Cloud Security Design Patterns