포스트

09.다중토큰 발급

LoginFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
    /* 단일토큰 방법
    MyUserDetails myUserDetails = (MyUserDetails) authentication.getPrincipal();
    String username = myUserDetails.getUsername();

    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
    GrantedAuthority auth = iterator.next();

    String role = auth.getAuthority();

    String token = jwtUtil.createJwt(username, role, 60*60*10L);

    response.addHeader("Authorization", "Bearer " + token);
    // 위의 HTTP인증방식은 RFC 7235 정의에 의해서 공식적으로 형식을 지정해줌
    // Authorization: 타입 인증토큰
    // Authorization: Bearer 인증토큰string
    // "Bearer "의 경우 공백을 끝에 붙여줘야 함
    */

    // 유저 정보 가져오기
    String username = authentication.getName();

    Collection<? extends GrantedAuthority> collection = authentication.getAuthorities();
    Iterator<? extends GrantedAuthority> iterator = collection.iterator();
    GrantedAuthority grantedAuthority = iterator.next();

    String role = grantedAuthority.getAuthority();

    String access = jwtUtil.createJwt("access", username, role, 6000000L);
    String refresh = jwtUtil.createJwt("refresh", username, role, 86400000L);

    // 응답 설정
    response.setHeader("access", access);
    response.addCookie(createCookie(refresh));
    // creatCookie() 메소드 구현
    response.setStatus(HttpStatus.OK.value());

}

private Cookie createCookie(String value){

  Cookie cookie = new Cookie("refresh", value); // 쿠키생성
  cookie.setMaxAge(24*60*60); // 쿠키의 생명주기, refresh 토큰의 생명주기와 동일하게 넣기
// cookie.setSecure(true); // Https 사용시 설정
// cookie.setPath("/"); // 쿠키가 적용될 범위를 설정할 수 있다.
  cookie.setHttpOnly(true); // 프론트에서 자바스크립트로 쿠키에 접근 할 수 없도록 설정

  return cookie;
}
  • 기존의 단일토큰으로 발급했던 부분을 토큰을 2개 생성하는 것으로 로직 변경
  • JWTUtil에서 정의한 토큰생성 메소드에서 category라는 키값을 추가하여 토큰 생성
  • ATK은 헤더에 발급 후 프론트에서 로컬 스토리지에 저장
    • XSS 공격에 취약하지만 방어로직을 구현할 수 있고, ATK의 생명주기가 짧기 때문에 XSS 공격으로 탈취 되더라도 금방 해지됨
    • 빠르게 공격가능한 CSRF 보다는 차선책으로 XSS 공격을 받겠다는 것
  • RTK는 쿠키에 저장
    • 쿠키는 httpOnly를 통해 XSS 공격은 방어가 가능하고 CSRF의 공격에 취약하지만, RTK는 재발급을 위한 로직으로 설정되어 있으니
    • CSRF를 통해 경로를 들어와도 상대적으로 문제될 가능성이 적다
  • RTK를 쿠키에 저장하기 위한 쿠키 생성 로직 추가

JWTUtil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public String createJwt(String category, String username, String role, Long expiredMs){
  Claims claims = Jwts.claims();
  claims.put("category", category);
  claims.put("username", username);
  claims.put("role", role);

  return Jwts.builder()
          .setClaims(claims)
          .setIssuedAt(new Date(System.currentTimeMillis()))
          .setIssuer(issuer)
          .setExpiration(new Date(System.currentTimeMillis() + expiredMs))
          .signWith(key, SignatureAlgorithm.HS256)
          .compact();
}

public String getCategory(String token){
  return Jwts.parserBuilder().setSigningKey(key).build()
          .parseClaimsJws(token).getBody().get("category", String.class);
}
  • Claims 객체에 category 속성을 추가
  • category는 ATK, RTK 을 구별하는 키값
  • 그리고 추후 프론트에서 JWT를 보낼 때 토큰을 구분하기 위한 getCategory() 메소드 추가
참고 사이트

개발자 유미