JWT 토큰발행(with Spring)

도준혁·2022년 4월 26일
1

JWT란?

Json Web Token 를 뜻하며 기업에서 많이 사용 중인 토큰 방식의 로그인 구현을 위한 토큰 발행 API이다. 예전에 프로젝트를 할 때 NodeJS 환경에서 발급하였는데 이번에는 Spring 에서 발급 및 해독하는 방법을 코드단위에서 알아보려고 한다.

JWT의 구성과 작동 방식은 다음번에 라이브러리 없이 직접 발행하는 과정을 실습하며 포스팅할 예정이다.

Project 환경

  • SpringBoot
  • Maven
  • Java 11

Dependency 설정

SpringBoot initializer 로 프로젝트 생성시에 JWT 관련 모듈을 미리 다운받아도 되고, xml 파일에 직접 작성해주고 리로드해도 상관없다. 글쓴이는 미리 생성해둔 프로젝트에 모듈을 미리 다운받지 않았기 때문에 다음과 같이 수동으로 코드를 추가해 주었다.

#pom.xml

<!-- JWT -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>

이로써 우리는 "Jwts"라는 Api를 Spring 에서 사용할 수 있게 되었다.

JWT 토큰 생성

JWT 토큰은 Header, Payload(claim), Signature 로 이루어져 있으며 최종 결과물에서 "."으로 구분한다. 자세한 내용은 다음 포스트에서 다룰 것이므로 이해가 안간다면 우선 외우자!

다음은 아까 추가하였던 Jwts 모듈을 호출하여 JWT 토큰을 생성하는 예제이다.

@Slf4j
@Controller
@RequestMapping("/v5")
public class JwtTestController {

    @GetMapping("/jwt")
    public String jwtTestForm() {
        return "v5/jwtTest";
    }

    @PostMapping("/jwt")
    public String successForm(Model model, @RequestParam String email, String username) {

        //salt
        String salt = "saaalt";

        //make jwt header
        Map<String, Object> jwtHeader = new HashMap<>();
        jwtHeader.put("typ", "JWT");
        jwtHeader.put("alg", "HS256");
        jwtHeader.put("regDate", System.currentTimeMillis());

        //make claim
        Map<String, Object> claim = new HashMap<>();
        claim.put("email", email);
        claim.put("username", username);

        String token = Jwts.builder()
                .setSubject(email)
                .setHeader(jwtHeader)
                .setClaims(claim)
                .signWith(SignatureAlgorithm.HS256, salt)
                .compact();

        model.addAttribute("token", token);
        return "/v5/verifyTest";
    }
}

본인은 v5/verifyTest 라는 jsp 페이지로 토큰을 내려주어 시각적으로 확인하기 위한 컨트롤러를 작성하였다. (아무 설명없이 토큰 발행 코드만 제시하면 오해의 소지가 있을 것 같아 컨트롤러 전문을 가져왔다.)

발행시에는 위에서 언급했던 Header, Payload(claim), Signature 를 만들어주는 과정이 들어간다.

Header 에는 이 토큰이 JWT 토큰임을 밝히며(typ) 내가 사용하고 싶은 시그니처의 암호화 방식(alg)을 명시한다.

Payload(claim) 부분에서는 내가 실제로 이 유저를 식별할 수 있는 "최소한" 의 정보만 담아준다. "최소한" 이라고 한 이유는 JWT 는 앞의 두 정보 즉 헤더와 페이로드를 단순히 base64 방식으로만 인코딩해서 전달하고 실질적인 암호화는 Signature 부분에서만 일어나기 때문에 유저 민감 정보를 "절대로" 담아서 서비스 하면 안된다.

절대로 "비밀번호" 같은 민감한 정보 넣으면 안된다!


마지막으로 `Signature` 생성에서 주의할 점은 토큰 발행시에 `secret key(salt)` 를 서명부분에 같이 첨가해야한다는 점이다. 이 `salt` 는 토큰을 유일한 것으로 만들어 주는 사이닝과 동시에 키 없이는 해독할 수 없는 (정확하게는 SHA256 방식의 해싱방식으로 브루털포스 방식으로 해독하려 한다면 일반적인 성능의 컴퓨터로는 일주일 이상의 시간이 소요된다고 한다.) 토큰을 발행하게 된다. 마찬가지로 `salt` 도 탈취되지 않도록 환경변수나 안전한 곳에 넣어 관리하도록 하자.

JWT 토큰 해독

토큰 해독도 마찬가지로 Jwts 모듈에서 Signature를 만들때 사용했던 secret key(salt)를 첨가해주면 된다.

@Controller
@RequestMapping("/v5")
public class JwtVerifyController {

    @PostMapping("/verify")
    public String verifyJWT(Model model, @RequestParam String token) {
        Claims verified = Jwts.parser().setSigningKey("saaalt").parseClaimsJws(token).getBody();
        model.addAttribute("userInfo", verified);
        return "/v5/verifyResult";
    }
}

마찬가지로 v5/verifyResult 라는 페이지에서 시각적으로 확인하기 위하여 컨트롤러를 작성하였다. 해독과정은 미리 다 지정해준 방식으로 되돌리기만 하면 되기 때문에 비교적 간단하다. 그리고 실험은 해보지 않았지만 HS256방식이 JWT의 디폴트 알고리즘이기 때문에 파싱과정에서 별다른 알고리즘을 기입하지 않아도 잘 작동했던것 같다. (예전에 NodeJS 에서는 SSL 인증서 방식으로도 해보았는데 그런과정이 없었기 때문에..)

3줄 요약

  1. JWT 는 유명한 웹 토큰의 한 종류이다.
  2. Header, Payload(claim), Signature 로 이루어져있다.
  3. "절대로" 비밀번호 payload에 넣지 말자!

profile
ML Ops 와 백엔드를 개발하고 있는 도준혁입니다

0개의 댓글