티스토리 뷰

지난번에도 JWT 관련 인증 내용을 다룬적이 있었다. 

 

Spring Security + JWT 인증 초간단 연동 예제

Monolithic Architecture에서 Micro Service Architecture까지는 아니더라도 많은 부분이 API로 분리가 되고 이에 대한 인증의 필요성이 생겼다. Spring Security와 JWT를 이용하면 이런 API 서버간의 인증을 간단..

oingdaddy.tistory.com

초간단 연동 예제라고 했는데 뭐가 초간단이냐며 욕도 먹고 해서 이번에는 정말 다 빼고 내가 통신할 API 서버와 JWT 인증하는 부분에 대해서만 간략히 기술하려한다. 


Springboot로 샘플 프로젝트를 2개 만들었다. 하나는 API를 호출하는 역할을 하는 A 프로젝트, 다른 하나는 A로부터 호출을 받아 값을 주는 역할을 하는 B 프로젝트. B 서버는 전형적인 API 서버라고 생각하면 된다.

jwt api 서비스 인증

이런 과정에 대해 샘플을 만들고 테스트를 할 것이다. 


환경 구성 (A, B 프로젝트 공통)

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
		
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

기본적으로 필요한 dependency는 spring-boot-starter-web과 jjwt가 필요하다. jjwt는 java json web token 인걸로 알고 있다. 

 

application.yml (A, B 프로젝트 공통)

server:
  port: 8080     #2번 서버는 8081

jwt:
  algorithm: 'HS256'
  expirTime: 1800000
  headerKey: 'Oing-Token'
  secretKey: 'oingispretty'

두 프로젝트 모두 application.yml에 이와 같은 내용을 기술한다. 하드코딩을 방지하기 위해 설정으로 필요한 값들을 뺐다. 꼭 이런 key가 아니더라도 원하는 값으로 변경해서 사용해도 된다. 

 

JwtUtil.java (A, B 프로젝트 공통)

@Component
public class JwtUtil {
    
    @Value("${jwt.secretKey}")
    private String secretKey;
    @Value("${jwt.algorithm}")
    private String algorithm;
    @Value("${jwt.expirTime}")
    private long expirTime;

    //jwt 생성
    public String buildAccessJwt(String fruitName) {
        
        JwtBuilder jwtBuilder = Jwts.builder();
        Map<String, Object> paramMap = new HashMap();
        
        // header
        jwtBuilder.setHeaderParam("alg", algorithm);
        
        // payload
        paramMap.put("fruitName", fruitName);
        jwtBuilder.setClaims(paramMap);
        jwtBuilder.setExpiration(new Date(new Date().getTime() + expirTime)); // 만료일
        
        // signature
        jwtBuilder.signWith(SignatureAlgorithm.forName(algorithm), secretKey.getBytes());
        
        return jwtBuilder.compact();
    }
    
    //jwt 유효성 판단
    public String isValidJwt(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token);            
            return "00";   // 유효
        } catch (ExpiredJwtException exception) {
            return "01";   // 만료
        } catch (JwtException exception) {
            return "02";   // 변조
        } catch (Exception e) {
            return "03";   // 그 외 token 오류
        }
    }
    
    
    //jwt body부 꺼내기
    public String getFruit(String token) {
        String fruitName = "";
        fruitName = (String) Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token).getBody().get("fruitName") ;
        return fruitName;
    }
}

아주 간단한 JwtUtil을 만들었다. JWT를 만들어주고 유효한지 판단해주고 payload에 들은 값을 꺼내주는 역할을 한다. 필요에 따라 JWT에 관련된 여러가지 기능을 넣어서 사용할 수 있다. 마지막에 getFruit 이라는 메소드가 있는데 필자가 이번에 예를 들기 위해 어설프게 만든 부분이다. 이해를 돕기 위한 예제를 만드는건 상당한 에너지가 소모된다.. 무슨 시나리오로 가야 할지 고민하는 시간이 더 많은것 같다. 

 

여기까지 했으면 A, B 프로젝트 공통으로 작업해야 하는 부분은 끝났다. 


A 프로젝트

AProjectController.java

@RestController
public class AProjectController {
    
    @Autowired
    JwtUtil jwtUtil;
    
    @Value("${jwt.headerKey}")
    private String headerKey;

    @GetMapping("/getFruitA/{fruitName}")
    public String getFruit(@PathVariable String fruitName) {
        HttpHeaders header = new HttpHeaders();
        header.add(headerKey, jwtUtil.buildAccessJwt(fruitName));
        ResponseEntity<String> response = new RestTemplate().exchange("http://localhost:8081/getFruitB", HttpMethod.GET, new HttpEntity<Object>(header), String.class);
        return response.getBody();
    }
}

요청을 보내기 위한 Controller를 하나 만들었다. 과일 이름을 입력받으면 그 값을 JWT에 포함시켜 B 서비스로 요청을 보낼 것이다. jwtUtil.buildAccessJwt로 생성한 JWT를 HttpHeader에 넣어서 RestTemplate을 통해 B 서비스로 보낸다. 그리고 B로부터 응답이 오면 그것을 뿌려주는 역할을 하는 Controller이다. 

즉 이 Controller는 토큰을 만들어서 Http 헤더에 넣고 보내주는 역할만 수행한다. (물론 토큰을 만들때는 아까 application.yml 파일에서 약속한 값들을 토대로 만든다.)


B 프로젝트

JwtFilter.java

@Component
public class JwtFilter extends OncePerRequestFilter {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @Value("${jwt.headerKey}")
    private String headerKey;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
            FilterChain filterChain) throws ServletException, IOException {
        String token = httpServletRequest.getHeader(headerKey);
        String result = jwtUtil.isValidJwt(token);
        
        if ("00".equals(result)) {
            String fruitName = jwtUtil.getFruit(token);
            httpServletRequest.setAttribute("fruitName", fruitName);
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } else if("01".equals(result)) {
            System.out.println("=================================== expired");
        } else {
            System.out.println("=================================== something wrong");
        }
    }
}

A가 보낸 요청을 B가 가장 먼저 받아보는 곳은 Filter이다. 그래서 JwtFilter라는 것을 만들어서 JWT로 들어온 요청에 대한 검증과 payload에 담긴 값을 꺼내어 request에 담는 역할을 한다. 이해를 돕기 위해 만든거라 JWT 외의 기능은 건성건성 만들었으니 오류에 대한 처리 부분을 비롯해 나머지 부분은 각자 완성하도록 하자. 

한줄 요약하자면 이 필터는 토큰이 유효한지 검증하고 유효하면 안의 값을 꺼내어 request에 담는 역할을 해준다. 

 

사실 여기까지만 봐도 되지만 예제의 완성을 위해 다음 부분까지 보고 마무리하도록 하겠다. 

 

BProjectController.java

@RestController
public class BProjectController {
    
    @GetMapping("/getFruitB")
    public String getFruit(HttpServletRequest req) {
        String fruitName = req.getAttribute("fruitName").toString();
        if(fruitName.equals("banana")) {
            fruitName = "yes! my favorite fruit is " + fruitName;
        } 
        return fruitName;
    }
}

Filter로부터 넘어온 req를 받아서 값을 꺼내어 A에게 뭐라도 돌려주려고 노력한 예제코드이다.. banana라는 값이 들어왔을때만 뭐라고 더 붙여서 리턴해준다. 

 

테스트는 간단하다. localhost:8080/getFruitA/banana 를 호출해보면 yes~ 어쩌고 하는 리턴을 보면 성공이다. JWT 인증이 정상적으로 된 것이다. 그리고 localhost:8081/getFruitB 도 호출해보도록 하자. Filter에서 인증이 유효하지 않다고 판단하여 막힌다. 

 

이런 흐름과 원리구나 하는 정도 이 예제를 보고 익혔으면 JWT에 대해 반이상은 이해한거라고 보면 된다. 

 

혹시 payload부를 암호화 해서 전송하고 복호화해서 보고 하는 것은 다음 글을 참조하도록 하자. 

 

JWT payload (claims, body) 부 암호화 및 복호화 방법

JWT를 사용하는것 자체가 암호화된 json 문자열로 전송을 하는건데 무슨 암호화 및 복호화를 또 하느냐!? 암호화가 된다 하더라도 jwt.io 에서 알고리즘만 알고 있다면 다시 복호화된 값으로 볼수

oingdaddy.tistory.com

 

끝!

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
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