본문 바로가기

Program language/JAVA_SpringBoot

Spring Boot의 Api에 JWT 적용하기 (feat. Gradle)

Spring Boot에서 크롤링 데이터를 불러오기 위한 Api 작업을 하였습니다.

 

이제 공공 api와 같이 인증된 사용자만 사용 할 수 있도록 인증키를 JWT를 사용하여 구현해보도록 하겠습니다.

 


개발 환경.

Mac OS /  IntelliJ / Gradle / PostgreSQL

 

사용중인 라이브러리.

Spring Boot / swagger / MyBatis / JWT

 


 

build.gradle 에서 의존성을 추가합니다.

compile 'io.jsonwebtoken:jjwt:0.9.0'

JwtController.java 파일을 만들겠습니다.

import com.example.apiserver.api.service.JwtService;
import com.example.apiserver.api.service.SelectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

@Controller
public class JwtController {

    @Autowired
    private JwtService jwtService;

    @Autowired  // MyBatis와 연결되어 있는 부분.
    private SelectService selectService;

    @RequestMapping(value="/jwt/create", method= RequestMethod.POST)
    public String createJwt(HttpServletRequest res) throws Exception {
        String jwt = jwtService.makeJwt(res);
        System.out.println(jwt);

        return "check";
    }

    @GetMapping("/jwt/auth")
    public boolean authToken(HttpServletRequest res) throws Exception {
        String jwt = res.getParameter("jwt");

        if(jwt == null) {
            return false;
        } else {
            return jwtService.checkJwt(jwt);
        }
    }

}

controller 에서는 토큰 생성을 위한 주소(/jwt/create)에 post 방식으로 필요한 데이터를 받아서 토큰을 생성 합니다.

return "check";는 토큰 생성 후 토큰을 체크하기 위해 제가 임시로 만들어 놓은 페이지로 이동하기 위한 코드 입니다.

 

/jwt/auth 에서는 get 방식으로 접근할때 전달받은 'jwt'라는 파라미터를 가져고 와서 null을 확인하고 null이 아닐 경우

정상 토큰인지 확인하기 위한 클래스로 이동합니다.


JwtService.java 파일 생성 (인터페이스 객체로 생성)

import javax.servlet.http.HttpServletRequest;

public interface JwtService {
    public String makeJwt(HttpServletRequest res) throws Exception;
    public boolean checkJwt(String jwt) throws Exception;
}

인터페이스 객체이기에 별건 없습니다.


JwtServiceImpl.java 파일 생성

import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class JwtServiceImpl implements JwtService {

    private String secretKey = "MySecretKeyWelcomeMyFirstJwt";

    private Logger logger = LoggerFactory.getLogger(JwtServiceImpl.class);

    @Override
    public String makeJwt(HttpServletRequest res) throws Exception {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        Date expireTime  = new Date();
        expireTime.setTime(expireTime.getTime() + 1000 * 60 * 1);
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(secretKey);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        Map<String, Object> headerMap = new HashMap<String, Object>();

        headerMap.put("typ", "JWT");
        headerMap.put("alg", "HS256");

        Map<String, Object> map = new HashMap<String, Object>();

        String name = res.getParameter("name");
        String id = res.getParameter("id");

        map.put("name", name);
        map.put("id", id);

        JwtBuilder builder = Jwts.builder().setHeader(headerMap)
                .setClaims(map)
                .setExpiration(expireTime)
                .signWith(signatureAlgorithm, signingKey);

        return builder.compact();
    }

    @Override
    public boolean checkJwt(String jwt) throws Exception {
        try {
            Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(secretKey))
                    .parseClaimsJws(jwt).getBody();

            logger.info("토큰 정상");
            logger.info("expireTime :" + claims.getExpiration());
            logger.info("name :" + claims.get("name"));
            logger.info("Id :" + claims.get("id"));
            return true;
        } catch (ExpiredJwtException exception) {
            logger.info("토큰 만료");
            return false;
        } catch (JwtException exception) {
            logger.info("토큰 변조");
            return false;
        }
    }
}

JwtServiceImpl에서는 크게 2가지로 나누어 집니다.

토큰을 생성하는 makeJwt와 생성된 토큰을 확인하는 checkJwt 입니다.

 

makeJwt에서 토큰 만료 기간은 현재 날짜 이후로만 가능합니다.

+1000 : 1000ms = 1s (1초) 추가

* 60 : 앞의 1초와 계산되어 60초. 즉 1분을 설정.

* 1 : 토큰 만료기간을 1분으로 지정. (필요시 분 단위로 입력하기 위해 지정 되어있음. ex) 30 분 -> * 30)

 

JWT는 총 3부분으로 구분되어 있습니다. 각 부분은 사이 . (점)을 이용하여 구분 됩니다.

첫 번째로 헤더에는 타입과 암호화 형태인 'HS256'이 지정되어 있습니다.

두 번째로 내용에는 전달할 내용이 포함되어 있습니다. (ex. name, id 등)

세 번째로 서명에는 위 쪽에서 지정한 secretKey를 Base64로 암호화하여 저장합니다.

이렇게 만들어진 3부분을 .을 기준으로 한줄로 만들어서 토큰으로 사용합니다.

 

checkJwt에서는 실제로 요청이 들어오면 이게 정상 토큰인지 변조되지는 않았는지,

사용 기간이 만료된 오래된 토큰인지 확인하게 됩니다.

복호화 방법은 위에 지정된 secretKey를 이용하게 됩니다.

그러므로 토큰을 위조해서 인증을 받을 수 없게 됩니다.

 

끝.