Spring SecurityとAWS Cognitoの連携
はじめに
AWS Cognitoは、ユーザー認証とアクセス制御を提供するマネージドサービスです。この記事では、Spring SecurityとAWS Cognitoを連携させ、安全な認証・認可システムを構築する方法について解説します。
基本的な設定
1. 依存関係の追加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-cognitoidp</artifactId>
<version>1.12.261</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
2. AWS Cognitoの設定
@Configuration
public class CognitoConfig {
@Value("${aws.cognito.region}")
private String region;
@Value("${aws.cognito.userPoolId}")
private String userPoolId;
@Value("${aws.cognito.clientId}")
private String clientId;
@Bean
public AWSCognitoIdentityProvider cognitoClient() {
return AWSCognitoIdentityProviderClientBuilder.standard()
.withRegion(region)
.build();
}
}
認証の実装
1. Cognito認証プロバイダーの実装
@Component
public class CognitoAuthenticationProvider implements AuthenticationProvider {
@Autowired
private AWSCognitoIdentityProvider cognitoClient;
@Value("${aws.cognito.userPoolId}")
private String userPoolId;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
try {
AdminInitiateAuthRequest authRequest = new AdminInitiateAuthRequest()
.withUserPoolId(userPoolId)
.withClientId(clientId)
.withAuthFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
.withAuthParameters(new HashMap<String, String>() {{
put("USERNAME", username);
put("PASSWORD", password);
}});
AdminInitiateAuthResult authResult = cognitoClient.adminInitiateAuth(authRequest);
if (authResult.getAuthenticationResult() != null) {
return new UsernamePasswordAuthenticationToken(
username,
password,
getAuthorities(authResult.getAuthenticationResult().getIdToken())
);
}
} catch (Exception e) {
throw new BadCredentialsException("認証に失敗しました", e);
}
throw new BadCredentialsException("認証に失敗しました");
}
private Collection<? extends GrantedAuthority> getAuthorities(String idToken) {
// JWTトークンから権限情報を抽出
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
2. セキュリティ設定
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CognitoAuthenticationProvider cognitoAuthenticationProvider;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(cognitoAuthenticationProvider)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(jwtAuthenticationConverter())
)
);
return http.build();
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthoritiesClaimName("cognito:groups");
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
}
ユーザー管理
1. ユーザー登録
@Service
public class CognitoUserService {
@Autowired
private AWSCognitoIdentityProvider cognitoClient;
@Value("${aws.cognito.userPoolId}")
private String userPoolId;
public void registerUser(UserRegistrationDto dto) {
try {
AdminCreateUserRequest createUserRequest = new AdminCreateUserRequest()
.withUserPoolId(userPoolId)
.withUsername(dto.getUsername())
.withTemporaryPassword(dto.getPassword())
.withUserAttributes(
new AttributeType().withName("email").withValue(dto.getEmail()),
new AttributeType().withName("email_verified").withValue("true")
);
cognitoClient.adminCreateUser(createUserRequest);
// パスワードを永続化
AdminSetUserPasswordRequest setPasswordRequest = new AdminSetUserPasswordRequest()
.withUserPoolId(userPoolId)
.withUsername(dto.getUsername())
.withPassword(dto.getPassword())
.withPermanent(true);
cognitoClient.adminSetUserPassword(setPasswordRequest);
} catch (Exception e) {
throw new RuntimeException("ユーザー登録に失敗しました", e);
}
}
}
2. ユーザー認証
@Service
public class CognitoAuthService {
@Autowired
private AWSCognitoIdentityProvider cognitoClient;
public AuthenticationResult authenticateUser(String username, String password) {
try {
AdminInitiateAuthRequest authRequest = new AdminInitiateAuthRequest()
.withUserPoolId(userPoolId)
.withClientId(clientId)
.withAuthFlow(AuthFlowType.ADMIN_USER_PASSWORD_AUTH)
.withAuthParameters(new HashMap<String, String>() {{
put("USERNAME", username);
put("PASSWORD", password);
}});
AdminInitiateAuthResult authResult = cognitoClient.adminInitiateAuth(authRequest);
return authResult.getAuthenticationResult();
} catch (Exception e) {
throw new RuntimeException("認証に失敗しました", e);
}
}
}
トークン検証
1. JWTトークンの検証
@Component
public class CognitoJwtValidator {
@Value("${aws.cognito.userPoolId}")
private String userPoolId;
@Value("${aws.cognito.region}")
private String region;
public Jwt validateToken(String token) {
try {
String jwksUrl = String.format(
"https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json",
region,
userPoolId
);
JwkProvider provider = new UrlJwkProvider(new URL(jwksUrl));
Jwt jwt = Jwts.parserBuilder()
.setSigningKeyResolver(new SigningKeyResolverAdapter() {
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
try {
return provider.get(header.getKeyId()).getPublicKey();
} catch (Exception e) {
throw new RuntimeException("トークンの検証に失敗しました", e);
}
}
})
.build()
.parseClaimsJws(token);
return jwt;
} catch (Exception e) {
throw new RuntimeException("トークンの検証に失敗しました", e);
}
}
}
ベストプラクティス
-
セキュリティ設定
- 適切なCORS設定
- セキュリティヘッダーの設定
- トークンの有効期限管理
-
エラーハンドリング
- Cognitoのエラーコードに応じた適切な処理
- ユーザーフレンドリーなエラーメッセージ
-
トークン管理
- リフレッシュトークンの適切な処理
- トークンの安全な保存
-
ユーザー管理
- パスワードポリシーの設定
- 多要素認証の実装
- アカウントロックアウトの設定
実装例:ログインコントローラー
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private CognitoAuthService authService;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
AuthenticationResult authResult = authService.authenticateUser(
request.getUsername(),
request.getPassword()
);
return ResponseEntity.ok(new LoginResponse(
authResult.getIdToken(),
authResult.getAccessToken(),
authResult.getRefreshToken()
));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new ErrorResponse("認証に失敗しました"));
}
}
}
まとめ
Spring SecurityとAWS Cognitoを連携させることで、安全でスケーラブルな認証・認可システムを構築することができます。適切な設定と実装により、企業レベルのセキュリティを実現できます。
参考資料
- AWS Cognito公式ドキュメント
- Spring Security公式ドキュメント
- OWASPセキュリティガイドライン