eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiUk9MRV9VU0VSIn0.
1234567890abcdef
디코딩 한다면
{"alg":"HS256","typ":"JWT"}{"userId":"123","role":"ROLE_USER"}5~9=Ѧu
Authorization: Bearer {token}으로 사용@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
return ResponseEntity.ok(authService.login(request));
}
}
public record LoginRequest(String email, String password) {
}
public record TokenResponse(String accessToken, String refreshToken) {
}
@Component // Spring Bean으로 등록해 다른 컴포넌트에서 주입 받을 수 있게 해줌
public class JwtTokenProvider {
// jwt.secret 값을 주입받음 -> 사용자가 아무 값이나 지정해주면 됨(한 20자리 정도?)
// ex. jwtsecretjwtsecretjwtsecretjwtsecretjwtsecretjwtsecretjwtsecretjwtsecret
@Value("${jwt.secret}")
private String secret;
// 엑세스 토큰 유효기간 설정 ms단위임!
private final long accessTokenValidity = 1000L * 60 * 60; // 1시간
// 리프레쉬 토큰 유효기간 설정 역시 ms단위임!
private final long refreshTokenValidity = 1000L * 60 * 60 * 24 * 7; // 7일
// 사용자 ID와 역할을 바탕으로 액세스 토큰을 생성 -> 최대한 적은 정보가 들어가는게 좋음
public String createAccessToken(Long userId, String role) {
return createToken(userId, role, accessTokenValidity);
}
// ID와 역할을 바탕으로 뭐? 리프레쉬 토큰을 만들어 준다~
public String createRefreshToken(Long userId, String role) {
return createToken(userId, role, refreshTokenValidity);
}
// 공통 로직 -> 토큰 생성 claims(정보) 설정, 발급 시간/ 만료 시간 지정, 서명
private String createToken(Long userId, String role, long validity) {
Claims claims = Jwts.claims().setSubject(String.valueOf(userId)); // subject에 userId 설정
claims.put("role", role); //claims에 룰 추가
Date now = new Date(); // 지금 시간
Date expiry = new Date(now.getTime() + validity); // 만료 시간 계산
// JWT 빌더를 이용해서 토큰 생성해줌
return Jwts.builder()
.setClaims(claims) // 위에 열심히 적은 정보들
.setIssuedAt(now) // 발급 시간
.setExpiration(expiry) // 만료 시간
.signWith(SignatureAlgorithm.HS256, secret.getBytes()) // 서명 알고리즘 + secretkey 지정
.compact(); // 최종적으로 토큰 생성
}
// 토큰에서 사용자 ID(subject) 추출
public Long getUserId(String token) {
return Long.valueOf(parseClaims(token).getSubject());
}
// 토큰에서 룰 추출
public String getRole(String token) {
return parseClaims(token).get("role", String.class);
}
// 토큰이 유효한지 검사 -> 성공하면 혁며..아니고 ture, 실패하면 false
public boolean validateToken(String token) {
try {
parseClaims(token); // 내부적으로 예외 던짐
return true;
} catch (Exception e) {
return false;
}
}
// 토큰을 파싱해서 Claims 객체 반환 (위에 적은 jwt.secret 이용해서 검증)
private Claims parseClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(secret.getBytes()) // 서명 키
.build()
.parseClaimsJws(token) //JWT 파싱
.getBody(); // Claims 반환
}
}
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;
public TokenResponse login(LoginRequest request) {
User user = userRepository.findByEmail(request.email())
.orElseThrow(() -> new RuntimeException("유저 없음"));
if (!user.getPassword().equals(request.password())) {
throw new RuntimeException("비밀번호 틀림");
}
// 엑세스 토큰 생성
String accessToken = jwtTokenProvider.createAccessToken(user.getId(), user.getRole().name());
// 리프레쉬 토큰 생성
String refreshToken = jwtTokenProvider.createRefreshToken(user.getId(), user.getRole().name());
// 토큰 두개 담아서 반환
return new TokenResponse(accessToken, refreshToken);
}
}
Service에서 가장 중요한건 열심히 만든 토큰을 똑바로 주는것!@Configuration // 이 클래스가 Spring의 설정 클래스임을 알려줌
@EnableWebSecurity // Spring Security 기능을 활성화
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider; // JWT 관련 로직을 처리할 Provider 주입
@Bean // SecurityFilterChain을 Bean으로 등록
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화 (JWT 기반 인증 할꺼니까?)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 사용하지 않도록 설정 (역시 JWT 인증 이니까)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll() // /auth/** 경로는 인증 없이 접근 허용
.anyRequest().authenticated() // 그 외의 모든 요청은 인증 필요
)
.addFilterBefore(new JwtAuthFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // JWT 인증 필터를 Spring Security 필터 체인 앞단에 추가
return http.build(); // 최종 SecurityFilterChain 객체 생성 및 반환
}
}
// 매 요청마다 실행됨
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
public JwtAuthFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
// 실제 필터 동작을 정의함
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = resolveToken(request); // Authorization 헤더에서 토큰 추출
// 토큰이 존재하고 유효하면 인증 처리
if (token != null && jwtTokenProvider.validateToken(token)) {
Long userId = jwtTokenProvider.getUserId(token); // 토큰에서 userId 추출
String role = jwtTokenProvider.getRole(token); // 토큰에서 role 추출
// 인증 객체 생성: principal에 커스텀 AuthUser, credentials는 null, 권한 목록 설정
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
new AuthUser(userId, role),
null,
List.of(new SimpleGrantedAuthority(role))
);
// 인증 객체를 현재 SecurityContext에 설정 → 컨트롤러에서 @AuthenticationPrincipal 등으로 접근 가능
SecurityContextHolder.getContext().setAuthentication(authentication);
}
// 다음 필터로 요청 전달 -체인 계속 진행
chain.doFilter(request, response);
}
// Authorization 헤더에서 Bearer 토큰 파싱
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
return (bearer != null && bearer.startsWith("Bearer ")) ? bearer.substring(7) : null;
}
}
그 다음은 어떤게 있냐고요?
1. 리프레쉬 토큰을 저장 -> 로그아웃, 재발급 대비
2. 로그아웃 시 블랙리스트 처리
3. PasswordEncoder 적용
등이 있죠!
와 JWT도 정리하셨군요.. 대단하십니다