Day09 JWT
JWT
JWT의 정의
- JWT(JSON Web Token)는 JSON 객체를 사용하여 두 개체 간에 정보를 안전하게 전송하기 위한 컴팩트하고 독립적인 방식으로 주로 인증 및 정보 교환에 사용된다.
헤더(Header): 토큰의 유형(JWT)과 해싱 알고리즘(예: HMAC SHA256)을 지정한다.
페이로드(Payload): 토큰에 포함될 클레임(Claim)을 담는다. 클레임은 엔티티(일반적으로 사용자)와 관련된 정보이다.
서명(Signature): 토큰의 무결성을 검증하기 위해 사용된다. 헤더와 페이로드를 인코딩한 후 비밀 키를 사용하여 서명한다.
게시글, 멤버 폼 작성하기
JWT 토큰
기본설정하기
- gradle에 의존성 추가하기
1 2 3
implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
- yml 파일에 secretKey 설정하기
application.yml에 설정하고 싶은 문자열을 설정한다.
토큰 시간 설정을 숨겨서 사용자가 토큰 만료시간을 알지 못하게 한다.1 2 3 4 5
custom: jwt: secret: [내가치고싶은 어려운 문자열] accessToken: expirationSeconds: "#{60 * 10}"
- ut 파일 생성
Json <-> Map 변환을 위한 유틸리티 파일을 생성한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class Ut { public static class json { public static Object toStr(Map<String, Object> map) { try { return new ObjectMapper().writeValueAsString(map); } catch (JsonProcessingException e) { return null; } } } public static Map<String, Object> toMap(String jsonStr) { try { return new ObjectMapper().readValue(jsonStr, LinkedHashMap.class); } catch (JsonProcessingException e) { return null; } } }
JWT Provider 생성하기
- JWT 토큰을 생성하고 검증하는 Provider를 생성한다.
- 토큰을 생성하기 위해서는 헤더, 페이로드, 서명이 필요하다.
헤더 : 토큰의 유형(JWT)과 해싱 알고리즘(예: HMAC SHA256)을 지정한다.
페이로드 : 토큰에 포함될 클레임(Claim)을 담는다. 클레임은 엔티티(일반적으로 사용자)와 관련된 정보이다.
서명 : 토큰의 무결성을 검증하기 위해 사용된다. 헤더와 페이로드를 인코딩한 후 비밀 키를 사용하여 서명한다.1 2 3 4 5 6
Jwts.builder() .setHeaderParam("typ", "JWT") .claim("body", Ut.json.toStr(claims)) .setExpiration(accessTokenExpiresIn) .signWith(getSecretKey(), SignatureAlgorithm.HS512) .compact();
- 헤더는 생략가능하다.
- 서명은 SecretKey를 사용하여 생성한다.
1 2 3 4 5 6 7 8 9 10 11
public SecretKey getSecretKey() { if (cachedSecretKey == null) { cachedSecretKey = cachedSecretKey = _getSecretKey(); } return cachedSecretKey; } private SecretKey _getSecretKey() { String keyBase64Encoded = Base64.getEncoder().encodeToString(secretKeyOrigin.getBytes()); return Keys.hmacShaKeyFor(keyBase64Encoded.getBytes()); }
- JwtProvider코드
apiFilter 생성하기
- 기본적으로 securityConfig는 모든 url에 대해 허용 했기 때문에 api로 들어오는 요청은 필터링이 필요하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@Bean SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/api/**") .authorizeHttpRequests(authorizeRequests -> authorizeRequests .requestMatchers(HttpMethod.GET, "/api/*/articles").permitAll() .requestMatchers(HttpMethod.GET, "/api/*/articles/*").permitAll() .requestMatchers(HttpMethod.POST, "/api/*/members/login").permitAll() // 로그인은 누구나 가능, post 요청만 허용 .anyRequest().authenticated() ) .csrf(csrf -> csrf.disable()) // csrf 토큰 끄기 .httpBasic(httpBasic -> httpBasic.disable()) // httpBasic 로그인 방식 끄기 .formLogin(formLogin -> formLogin.disable()) // 폼 로그인 방식 끄기 .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); }
토큰을 쿠키에 담기
- JwtProvider을 통해 생성된 토큰을 response헤더에 담아 보내면 클라이언트는 쿠키를 보관하게된다.
- 발급받은 쿠키를 통해 로그인 시 인증을 받고, 권한이 필요한 요청에 대해 인가를 받는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
@PostMapping("/login") public RsData<Void> login(@Valid @RequestBody MemberRequest memberRequest, HttpServletResponse response) { Member member = memberService.getMember(memberRequest.getUsername()); // 토큰 생성 String token = jwtProvider.genAccessToken(member); // 응답 데이터에 accessToken 이름으로 토큰을 발급 response.addCookie(new Cookie("accessToken", token)); Cookie cookie = new Cookie("accessToken", token); cookie.setHttpOnly(true); cookie.setSecure(true); cookie.setPath("/"); cookie.setMaxAge(60 * 60); response.addCookie(cookie); return new RsData<>("200", "로그인에 성공하였습니다."); }
토큰을 이용해 회원정보 조회
- 클라이언트에서 받은 토큰을 복호화하여 클레임(Map 객체) 부분을 확인하다.
- 클레임에서 정보를 추출하여 조회를 한다.
- yml에 설정한 secret키를 활용하여 SercetKey를 생성하고, 무결성을 검증한다.
claims에서 body를 추출하여 Map으로 변환하고 JWT생성 시 담겻던 Body가 Map으로 변환된다.
1 2 3 4 5 6 7 8
public Map getClaims(String token) { String body = Jwts.parserBuilder() .setSigningKey(getSecretKey()) .build() .parseClaimsJws(token) .getBody() .get("body", String.class); return Ut.toMap(body);
토큰을 활용하여 인가처리하기
- 인가는 다음과 같은 처리 과정을 거친다 .
1.ApiSecurityConfig에서 설정한 apiFilterChain에서 인증이 필요한 요청에 대해 JwtAuthorizationFilter를 거친다. 2.JwtAuthorizationFilter는 쿠키에서 토큰을 가져와 검증한다.
- 토큰이 유효하다면 securityUser를 가져와 SecurityContext에 인증 객체로 저장한다.
1 2 3 4
Jwts.parserBuilder() .setSigningKey(getSecretKey()) // 서명 검증에 사용할 비밀키 설정 .build() // JWT 파서 생성 .parseClaimsJws(token); //검증한다.
- 토큰이 유효하지 않으면, refresh 토큰을 검증하여 기존 회원인지를 검사하여 새로운 accessToken을 발급한다.
- securityUser를 가져와 SecurityContext에 인증 객체로 저장한다.
1 2 3 4
// securityUser 가져오기 SecurityUser securityUser = memberService.getUserFromAccessToken(accessToken); // 인가 처리 SecurityContextHolder.getContext().setAuthentication(securityUser.genAuthentication());
- 토큰이 유효하다면 securityUser를 가져와 SecurityContext에 인증 객체로 저장한다.
- 코드
로그인 시 리프레시 발급
- 코드
로그아웃시 토큰 삭제
- 코드
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.