포스트

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);
    

    토큰을 활용하여 인가처리하기

  • 인가는 다음과 같은 처리 과정을 거친다 . image 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());
      
  • 코드

    로그인 시 리프레시 발급

  • 코드

    로그아웃시 토큰 삭제

  • 코드
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.