spring boot(Java) Jwt토큰 생성 및 복호화 하기 DEV / WEB
2020-07-02 posted by sang12
Java Jwt라이브러리를 이용하여 JWT토큰을 생성하고 복호화, 검증하는 방법을 알아보도록 하겠습니다. (JWT의 자세한 정보는 다른 설명 잘해둔 블로그들이 많아 생략하겠습니다.)
Spring boot환경에서 단위테스트로 진행하였습니다.
1. 일단 라이브러리를 사용하기 위해 pom.xml에 아래와 같이 추가합니다. 현재 최신버전은 3.10.3이네요 :)
-pom.xml
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
2. jwt 라이브러리를 이용하여 jwt 토큰생성 및 검증
Jwt 토큰을 생성하면 header.payload.signature 형태로 Base64인코딩이 되어 생성 됩니다. 그래서 payload에 있는 데이터를 조회하려면 Base64 디코딩을 해서 가져와야 합니다. 검증을 할때에는, 잘못된 키값을 사용하면 Exception을 던져주는 주니 해당 Exception을 잡아, jwt토큰을 검증 할 수 있습니다. spring boot에서는 필터에서 헤더에 있는 jwt토큰값을 받아 검증하는 형태로 처리하면 쉽게 로그인이나 보안 관련된 처리를 할 수 있습니다.
-JwtTest.java
public class JwtTest {
@Test
public void jwtTockenTest() {
// Create JWT Token
String token = JWT.create()
.withSubject("helloworld")
.withExpiresAt(new Date(System.currentTimeMillis() + 864000000))
.sign(Algorithm.HMAC512("test".getBytes()));
//jwt token header.payload.secret
System.out.println(token);
Decoder decoder = Base64.getDecoder();
byte[] decodedBytes = decoder.decode("eyJzdWIiOiJoZWxsb3dvcmxkIiwiZXhwIjoxNTk0NTQ5MzAxfQ".getBytes());
//payload
System.out.println(new String(decodedBytes));
//검증
try {
String result = JWT.require(Algorithm.HMAC512("test".getBytes()))
.build()
.verify(token.replace("Bearer", ""))
.getSubject();
//result
System.out.println(result);
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
자, 그럼 원하는 데이터를 jwt규격으로 만들어 전송하고 확인해보자. withClaim을 이용하여 원하는 데이터를 넣고, heade와 payload를 찍어 정상적으로 값이 전달 됬는지 확인해 볼 수 있다.
// Create JWT Token
String token = JWT.create()
.withSubject("helloworld")
.withExpiresAt(new Date(System.currentTimeMillis() + 864000000))
.withClaim("memberId","test")
.withSubject("회원id")
.sign(Algorithm.HMAC512("test".getBytes()));
//jwt token header.payload.signature
System.out.println(token);
Decoder decoder = Base64.getDecoder();
final String[] splitJwt = token.split("\\.");
final String headerStr = new String(decoder.decode(splitJwt[0].getBytes()));
final String payloadStr = new String(decoder.decode(splitJwt[1].getBytes()));
System.out.println(headerStr + "," + payloadStr);
//검증
try {
Claim result = JWT.require(Algorithm.HMAC512("test".getBytes()))
.build()
.verify(token.replace("Bearer", ""))
.getClaim("memberId");
//result
System.out.println(result.asString());
}catch(Exception e) {
System.out.println(e.getMessage());
}
3. 이제 spring boot에서 filter를 생성하여 head에서 들어온 token값을 출력해보겠습니다. postman같은걸 이용해서 head에 token값을 전송하여 토큰이 찍히는걸 확인해봅시다.
- FilterConfig.java
import java.util.Arrays;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.hanmi.onlinepharm.filter.JwtVerifyFilter;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean jwtVerifyFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new JwtVerifyFilter());
bean.setUrlPatterns(Arrays.asList("/api/*")); //testHello 요청으로 오는 애들만 필러링
return bean;
}
}
-JwtVerifyFilter.java
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class JwtVerifyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.debug("--------jwtFilter----------");
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
log.debug("token:::" + req.getHeader("token"));
chain.doFilter(request, response);
}
}
확인하셨나요? 그럼 아까 테스트코드에서 검증에 사용했던 소스를 약간 수정해서 Filter에서 Jwt 토큰을 검증하게 변경해봅시다.
-JwtUtil.java
@Slf4j
public class JwtUtil {
public static boolean verifyToken(String token, String secret) {
if(token == null) return false;
try {
String result2 = JWT.require(Algorithm.HMAC512(secret.getBytes()))
.build()
.verify(token.replace("Bearer", ""))
.getSubject();
//result
log.debug(result2);
return true;
}catch(Exception e) {
log.error("token값 인증 오류" + e.getMessage());
return false;
}
}
}
-JwtVerifyFilter 수정
@Slf4j
public class JwtVerifyFilter implements Filter {
private String secret;
private String header;
public JwtVerifyFilter(String secret, String header) {
this.secret = secret;
this.header = header;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.debug("--------jwtFilter----------");
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
//header에서 token값을 가져와서 true false를 리턴
if(JwtUtil.verifyToken(req.getHeader(header), secret)) {
chain.doFilter(request, response);
}else {
throw new RuntimeException();
}
}
}
-FilterConfig.Java 파일 수정 (secret, header값 주입)
@Configuration
public class FilterConfig {
@Value("${nugu.jwt.secret}")
private String secret;
@Value("${nugu.jwt.header}")
private String header;
@Bean
public FilterRegistrationBean jwtVerifyFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new JwtVerifyFilter(secret, header));
bean.setUrlPatterns(Arrays.asList("/api/*"));
return bean;
}
}
이제 다시, token을 담아 전송하면 filter에서 체크하는것을 볼 수 있습니다. 저는 token인증이 안되었을때 개인적으로 만든 사용자 Exception을 던져, 토큰정보가 인증되지 않았다는 메세지를 전달했습니다 :)