본문 바로가기

WEB개념

[WEB] JWT vs JSESSIONID

JWT

: JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화시킨 토큰을 의미합니다. JWT 기반 인증은 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별합니다.

 

 

JWT 구조

 

JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합입니다. 실제 디코딩된 JWT는 다음과 같은 구조를 지닙니다.

 

Header


Header는 alg과 typ는 각각 정보를 암호화할 해싱 알고리즘 및 토큰의 타입을 지정합니다.

Payload


Payload는 토큰에 담을 정보를 지니고 있습니다.

주로 클라이언트의 고유 ID 값 및 유효 기간 등이 포함되는 영역입니다.

key-value 형식으로 이루어진 한 쌍의 정보를 Claim이라고 칭합니다.

 

 

 단점

  1. 쿠키/세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해집니다.
  2. Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없습니다.
  3. 토큰을 탈취당하면 대처하기 어렵습니다.
  4. 토큰은 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문입니다.
  5. 특정 사용자의 접속을 강제로 만료하기 어렵지만, 쿠키/세션 기반 인증은 서버 쪽에서 쉽게 세션을 삭제할 수 있습니다.

 

 

application.properties

 

jwt.expiration_time=86400000
jwt.secret=VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHa

 

 

build.gradle

 

implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.12.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.12.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.12.5'

 

 

 

JAVA

 

/**
 * [JWT 관련 메서드를 제공하는 클래스]
 */
@Log4j2
@Component
public class JWTUtil {

    private final Key key;
    private final long accessTokenExpTime;

    public JWTUtil(
            @Value("${jwt.secret}") String secretKey,
            @Value("${jwt.expiration_time}") long accessTokenExpTime
    ) {
        byte[] keyBytes = Decoders.BASE64.decode(secretKey);
        this.key = Keys.hmacShaKeyFor(keyBytes);
        this.accessTokenExpTime = accessTokenExpTime;
    }

    /**
     * Access Token 생성
     * @param user
     * @return Access Token String
     */
    public String createAccessToken(UserInfo user) {
        return createToken(user, accessTokenExpTime);
    }


    /**
     * JWT 생성
     * @param user
     * @param expireTime
     * @return JWT String
     */
    private String createToken(UserInfo user, long expireTime) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", user.getUserId());
        claims.put("userName", user.getUserName());

        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime tokenValidity = now.plusSeconds(expireTime);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(Date.from(now.toInstant()))
                .setExpiration(Date.from(tokenValidity.toInstant()))
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }


    /**
     * Token에서 User ID 추출
     * @param token
     * @return User ID
     */
    public String getUserId(String token) {
        return parseClaims(token).get("userId", String.class);
    }


    /**
     * JWT 검증
     * @param token
     * @return IsValidate
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT Token", e);
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT Token", e);
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT Token", e);
        } catch (IllegalArgumentException e) {
            log.info("JWT claims string is empty.", e);
        }
        return false;
    }


    /**
     * JWT Claims 추출
     * @param token
     * @return JWT Claims
     */
    public Claims parseClaims(String token) {
        try {
            return  Jwts.parser().setSigningKey(key).build().parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            return e.getClaims();
        }
    }

}

 

@Log4j2
@RestController
@RequiredArgsConstructor
@RequestMapping("/jwt")
public class LoginController {

    private final JWTUtil jwtUtil;

    @RequestMapping("/login")
    public ResponseEntity<String> login(@RequestBody HashMap<String, Object> data) {

        String userId = (String) data.get("id");
        String userPassword = (String) data.get("pw");

        //로그인 과정 패스....
        UserInfo userInfo = new UserInfo();
        userInfo.setUserName("Wooyeon");
        userInfo.setUserId(userId);
        userInfo.setUserPassword(userPassword);

        String token = jwtUtil.createAccessToken(userInfo);
        return ResponseEntity.status(HttpStatus.OK).body(token);
    }


    @RequestMapping("/validate")
    public ResponseEntity<String> validate(HttpServletRequest request) {

        String authorizationHeader = request.getHeader("Authorization");

        //JWT가 헤더에 있는 경우
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            String token = authorizationHeader.substring(7);

            log.info("token..:" + token);

            //JWT 유효성 검증
            if (jwtUtil.validateToken(token)) {
                String userId = jwtUtil.getUserId(token);

                log.info("jwt login userId .... : " + userId);

            } else {

                log.info("jwt validate token fail");
            }
        }

        return ResponseEntity.status(HttpStatus.OK).body("SUCCESS");
    }

}

 

 

 

login Request

 

token validate request

 

 

 


 

Cookie

 

 : 쿠키란 클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일을 일컫습니다.

 

MockHttpServletResponse:
           Status = 200
          Headers = [Set-Cookie:"userName=kevin", "password=abc123"]

 

  • 서버는 클라이언트의 로그인 요청에 대한 응답을 작성할 때, 클라이언트 측에 저장하고 싶은 정보를 응답 헤더의 Set-Cookie에 담습니다.

  • 쿠키는 Key-Value 형식의 문자열입니다.

 

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /user/my/edit
          Headers = [Cookie:"userName=kevin"; "password=abc123"]

 

  • 이후 해당 클라이언트는 요청을 보낼 때마다, 매번 저장된 쿠키를 요청 헤더의 Cookie에 담아 보냅니다.
  • 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별할 수 있습니다.

 

단점

  • 보안에 취약합니다.
    • 요청 시 쿠키의 값을 그대로 보냅니다.
    • 유출 및 조작 당할 위험이 존재합니다.
  • 쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없습니다.
  • 웹 브라우저마다 쿠키에 대한 지원 형태가 다르기 때문에 브라우저간 공유가 불가능합니다.
  • 쿠키의 사이즈가 커질수록 네트워크에 부하가 심해집니다.

 

JAVA

@GetMapping("/getCookie")
public String getCookie(HttpServletRequest request,
        @CookieValue(value = "hi") Cookie cookie) {
    Cookie[] cookies = request.getCookies();

    Arrays.asList(cookies).stream()
            .forEach(c -> log.debug(c.getName() + ":" + c.getValue()));

    log.debug("cookie :{}", cookie.getValue());
    return "getCookie";
}

 

 

 

 


 

http://www.opennaru.com/opennaru-blog/jwt-json-web-token/

https://tecoble.techcourse.co.kr/post/2021-05-22-cookie-session-jwt/

https://sjh9708.tistory.com/170