依存関係の設定
プロジェクトに必要なライブラリを追加します。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
Redis接続設定
application.propertiesでRedis接続を定義します。
spring.redis.host=localhost
spring.redis.port=6379
JWT処理ユーティリティ
トークンの生成・検証ロジックを実装します。
import io.jsonwebtoken.*;
import java.util.Date;
import java.util.Map;
public class TokenProcessor {
private final String secretKey;
public TokenProcessor(String secret) {
this.secretKey = secret;
}
public String generateToken(Map<String, Object> payload) {
return Jwts.builder()
.setClaims(payload)
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
public boolean verifyToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
}
}
セキュリティ設定
Spring Securityの設定クラスを作成します。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private TokenProcessor tokenProcessor;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(
new JwtAuthFilter(tokenProcessor),
UsernamePasswordAuthenticationFilter.class
);
}
}
認証フィルター実装
リクエストごとのJWT検証処理を実装します。
public class JwtAuthFilter extends OncePerRequestFilter {
private final TokenProcessor tokenProcessor;
public JwtAuthFilter(TokenProcessor processor) {
this.tokenProcessor = processor;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
String token = extractToken(req);
if (token != null && tokenProcessor.verifyToken(token)) {
Claims claims = tokenProcessor.extractClaims(token);
String userId = claims.getSubject();
Authentication auth = new UsernamePasswordAuthenticationToken(
userId, null, Collections.emptyList()
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(req, res);
}
private String extractToken(HttpServletRequest req) {
String header = req.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}
セッション管理サービス
Redisを使用したセッション状態管理を実装します。
@Service
public class SessionService {
@Autowired
private StringRedisTemplate redisTemplate;
public void registerSession(String userId, String token) {
redisTemplate.opsForValue().set(
"user_session:" + userId,
token,
1, TimeUnit.HOURS
);
}
public boolean isActiveSession(String userId, String token) {
String activeToken = redisTemplate.opsForValue().get("user_session:" + userId);
return token.equals(activeToken);
}
public void removeSession(String userId) {
redisTemplate.delete("user_session:" + userId);
}
}
認証フィルターの拡張
セッション状態チェックを追加します。
public class JwtAuthFilter extends OncePerRequestFilter {
// 既存のフィールドとメソッド
@Autowired
private SessionService sessionService;
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
String token = extractToken(req);
if (token != null && tokenProcessor.verifyToken(token)) {
Claims claims = tokenProcessor.extractClaims(token);
String userId = claims.getSubject();
if (!sessionService.isActiveSession(userId, token)) {
res.setStatus(HttpServletResponse.SC_CONFLICT);
res.getWriter().write("別のセッションがアクティブです");
return;
}
// 認証情報設定...
}
chain.doFilter(req, res);
}
}
ログアウト処理
セッション終了時のクリーンアップを実装します。
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private SessionService sessionService;
@PostMapping("/logout")
public ResponseEntity<String> logout(@RequestHeader("Authorization") String authHeader) {
String token = authHeader.substring(7);
Claims claims = tokenProcessor.extractClaims(token);
String userId = claims.getSubject();
sessionService.removeSession(userId);
return ResponseEntity.ok("ログアウトしました");
}
}
設計の主要ポイント
- JWT発行時にユーザー識別子をクレームに設定
- RedisにユーザーIDとトークンのペアを保存
- リクエストごとにRedisのアクティブセッションを検証
- セッション競合時はHTTP 409 Conflictを返却
- トークン有効期限とRedisのTTLを同期