feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
package com.gcsc.guide.auth;
|
|
|
|
|
|
|
|
|
|
import io.jsonwebtoken.Claims;
|
|
|
|
|
import io.jsonwebtoken.JwtException;
|
|
|
|
|
import io.jsonwebtoken.Jwts;
|
|
|
|
|
import io.jsonwebtoken.security.Keys;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
|
|
import javax.crypto.SecretKey;
|
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
import java.util.Date;
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
|
@Component
|
|
|
|
|
public class JwtTokenProvider {
|
|
|
|
|
|
|
|
|
|
private final SecretKey secretKey;
|
|
|
|
|
private final long expirationMs;
|
|
|
|
|
|
|
|
|
|
public JwtTokenProvider(
|
|
|
|
|
@Value("${app.jwt.secret}") String secret,
|
|
|
|
|
@Value("${app.jwt.expiration-ms}") long expirationMs
|
|
|
|
|
) {
|
|
|
|
|
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
this.expirationMs = expirationMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String generateToken(Long userId, String email, boolean isAdmin) {
|
|
|
|
|
Date now = new Date();
|
|
|
|
|
Date expiry = new Date(now.getTime() + expirationMs);
|
|
|
|
|
|
|
|
|
|
return Jwts.builder()
|
|
|
|
|
.subject(userId.toString())
|
|
|
|
|
.claim("email", email)
|
|
|
|
|
.claim("isAdmin", isAdmin)
|
|
|
|
|
.issuedAt(now)
|
|
|
|
|
.expiration(expiry)
|
|
|
|
|
.signWith(secretKey)
|
|
|
|
|
.compact();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Long getUserIdFromToken(String token) {
|
|
|
|
|
Claims claims = parseToken(token);
|
|
|
|
|
return Long.parseLong(claims.getSubject());
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-17 17:31:00 +09:00
|
|
|
public String getEmailFromToken(String token) {
|
|
|
|
|
Claims claims = parseToken(token);
|
|
|
|
|
return claims.get("email", String.class);
|
|
|
|
|
}
|
|
|
|
|
|
feat(auth): JWT 기반 Google 로그인 인증 API 구현
- Entity: User, Role, RoleUrlPattern, UserStatus enum
- Repository: UserRepository, RoleRepository (fetch join 쿼리)
- Auth: GoogleTokenVerifier, JwtTokenProvider, JwtAuthenticationFilter
- API: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout
- DTO: AuthResponse, UserResponse, RoleResponse, GoogleLoginRequest
- SecurityConfig: JWT 필터 등록, CORS 설정, 공개 엔드포인트 정의
- 초기 데이터: roles + role_url_patterns 시드 (data.sql)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 17:28:51 +09:00
|
|
|
public boolean validateToken(String token) {
|
|
|
|
|
try {
|
|
|
|
|
parseToken(token);
|
|
|
|
|
return true;
|
|
|
|
|
} catch (JwtException | IllegalArgumentException e) {
|
|
|
|
|
log.debug("JWT 토큰 검증 실패: {}", e.getMessage());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Claims parseToken(String token) {
|
|
|
|
|
return Jwts.parser()
|
|
|
|
|
.verifyWith(secretKey)
|
|
|
|
|
.build()
|
|
|
|
|
.parseSignedClaims(token)
|
|
|
|
|
.getPayload();
|
|
|
|
|
}
|
|
|
|
|
}
|