본문 바로가기
Programming/Java, Spring

[Spring Cloud] JWT 토큰 생성과 API Gateway에서 검증 필터 구현

by Renechoi 2023. 6. 1.

 

스프링 부트 프로젝트에서 

JWT 토큰을 생성하기 

 

 

dependencies 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
    implementation 'io.jsonwebtoken:jjwt-impl:0.11.2'
    implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
    // 다른 종속성들...
}

 

 

yml


token:
  expiration_time: 86400000
  secret: user_token

86400000 = 24시간 

 

 

 

Filter에서 JWT 생성해주는 메서드 

@Override
protected void successfulAuthentication(HttpServletRequest request,
    HttpServletResponse response,
    FilterChain chain,
    Authentication authResult) throws IOException, ServletException {
    String userName = ((User)authResult.getPrincipal()).getUsername();
    UserDto userDetails = userService.getUserDetailsByEmail(userName);

    String token = Jwts.builder()
        .setSubject(userDetails.getUserId())
        .setExpiration(new Date(System.currentTimeMillis() +
            Long.parseLong(env.getProperty("token.expiration_time"))))
        .signWith(SignatureAlgorithm.HS512, env.getProperty("token.secret"))
        .compact();

    response.addHeader("token", token);
    response.addHeader("userId", userDetails.getUserId());
}

 

 

 

 

 

토큰 방식이 필요한 이유

 

기존의 전통적인 인증 시스템은 username, password등의 정보를 요청하면 서버에서 이에 대한 인증을 처리하고 인증 성공시 session과 cookie를 설정하였다.

그런데 문제는 자바에서 발급하는 인증 세션이 타 기종에서는 호환이 안된다는 것이다.

 

모바일 어플리케이션 같은 경우 html 페이지가 아닌 Json이나 다른 포맷이 사용되는 것이 예이다. 

 

또한 다른 언어로의 호환시에도 공유 불가하다는 어려움이 있다. 

 

따라서 토큰으로서의 공통적인 인증 공유 방식이 필요하며, 이로서 토큰 기반 인증 시스템이 등장하였다. 

 

JWT = Json Web Token 

- 인증 헤더 내에서 사용되는 토큰 포맷

- 두 개의 시스템끼리 안전한 통신 가능

 

 

장점 

- 클라이언트 독립적인 서비스 (stateless)

- CDN

- No Cookie-Session (No csrf) 

- 지속적인 토큰 저장 

 

 

 

 

API Gateway에서 JWT 토큰 검증의 필요성

 

일반적인 회원가입과 로그인 시에는 post 요청으로 진행이 되며 아직 토큰이 없는 상태이기 때문에 별도의 작업이 필요하지 않다.

즉, 인증 로직에서는 토큰 검증이 필요하지 않다.

 

그러나 인가 로직에서, 즉 로그인한 유저에 대한 검증 이후 인증된 유저에 대한 정보를 반환해야 할 때는 필요하다. 

 

따라서 yml 파일에서 다음과 같이 filter를 추가해주고 이를 구현해준다. 

 

인증된 유저에 대한 GET 요청에 대한 검증을 위한 필터 추가 

- id: user-service
  uri: lb://USER-SERVICE
  predicates:
    - Path=/user-service/**
    - Method=GET
  filters:
    - RemoveRequestHeader=Cookie
    - RewritePath=/user-service/(?<segment>.*), /$\{segment}
    - AuthorizationHeaderFilter

 

 

API Gateway에서 jwt를 검증하는 필터 클래스 구현하기 

 


@Component
@Slf4j
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {

   Environment env;

   public AuthorizationHeaderFilter(Environment env) {

      super(Config.class);
      this.env = env;
   }

   public static class Config {
      // Put configuration properties here
   }

   @Override

   public GatewayFilter apply(Config config) {
      return (exchange, chain) -> {
         ServerHttpRequest request = exchange.getRequest();
         if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
            return onError(exchange, "No authorization header", HttpStatus.UNAUTHORIZED);
         }

         String authorizationHeader = request.getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
         String jwt = authorizationHeader.replace("Bearer", "");

         if (!isJwtValid(jwt)) {
            return onError(exchange, "JWT token is not valid", HttpStatus.UNAUTHORIZED);

         }
         return chain.filter(exchange);
      };

   }

	// mono, flux -> webflux : 단위 값이면 mono로 반환
	private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {

		ServerHttpResponse response = exchange.getResponse();
		response.setStatusCode(httpStatus);
		log.error(err);
		return response.setComplete();
	}


   private boolean isJwtValid(String jwt) {

      boolean returnValue = true;
      String subject = null;

      try {
         subject = Jwts.parser().setSigningKey(env.getProperty("token.secret"))
            .parseClaimsJws(jwt).getBody()
            .getSubject();
      } catch (Exception ex) {
         returnValue = false;
      }

      if (subject == null || subject.isEmpty()) {
         returnValue = false;
      }
      return returnValue;
   }

}

 

 

 


ref. https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%81%B4%EB%9D%BC%EC%9A%B0%EB%93%9C-%EB%A7%88%EC%9D%B4%ED%81%AC%EB%A1%9C%EC%84%9C%EB%B9%84%EC%8A%A4

반응형