MSA 5일차 : 스프링 클라우드 - JWT 적용 part1

parang·2025년 6월 1일

LG CNS AM Inspire Camp 2기

목록 보기
37/50
post-thumbnail

스프링 클라우드로 프로젝트 실습 중이고, 오늘은 JWT 적용에 대해 자세히 배웠다. JWT 부분 개념이 많이 부족한 지 어떻게 알고 ^^!

보안 용어

Authentication 인증

사용자가 누구인지 확인하는 단계.

ex. 로그인

Authorization 인가

인증을 통해 검증된 사용자가 리소스에 접근할 때, 리소스에 접슨할 권리가 있는지 확인하는 과정.

Principal

접근 주체.

토큰 기반 인증

토큰 인증 과정

  1. 클라이언트 ---> 로그인 요청 ---> 서버
  2. 서버 ---> 토큰 생성 ---> 클라이언트 ---> 토큰 저장
  3. 클라이언트 with 토큰 ---> 요청 (Authorization: Bearer Token) ---> 서버 ---> 토큰 검증
  4. 서버 ---> 응답 ---> 클라이언트

토큰은 Access Token, Refresh Token이 있다. 리프레시 토큰은 주로 엑세스 토큰이 만료되었을 때 새로운 엑세스 토큰을 발급하기 위해 사용된다. db에 저장되는 토큰이다.

MSA 에서 JWT 적용할 때 달라진 점은 토큰 발생은 USER 서비스가 하고 인증은 게이트웨이에서 한다는 점이다. 일단 적용해보자.

프로젝트에 JWT 적용

의존성 추가

implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-gson:0.12.5'

yml 설정

jwt:
	expires-in: 86400
    mobile-expires-in: 31536000 
    table-expires-in: 31536000 
    secret-key: AADfaskllew32dsfasdTG764aaaaaaGdslkj298GsWg86G

expires-in: 86400 웹은 유효기간을 하루로 설정.
시크릿 키는 20자 이상이어야 한다.

JwtConfigProperties.java

해당 값들을 변수로 접근하는데 사용할 클래스이다.

@Component
@ConfigurationProperties(value = "jwt", ignoreInvalidFields = true)
@Getter
@Setter
public class JwtConfigProperties {
    private Integer expiresIn;
    private Integer mobileExpiresIn;
    private Integer tabletExpiresIn;
    private String secretKey;
}

-> yml에서 설정한 값이 매핑된다.

TokenDto.java

토큰 정보를 담을 dto를 설정했다.

cf. 코드 중, @NoArgsConstructor(access = AccessLevel.PRIVATE) 이 부분은 외부에서 이 클래스(TokenDto)를 직접 new 하지 못하도록 막는 코드이다.


@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TokenDto {
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor 
    public static class JwtToken {
        private String token;
        private Integer expiresIn;
    }

    @Getter
    @RequiredArgsConstructor
    public static class AccessToken {
        private final JwtToken access;
    }

    @Getter
    @Setter
    @RequiredArgsConstructor
    public static class AccessRefreshToken {
        private final JwtToken access; 
        private final JwtToken refresh;
    }
}

TokenGenerator.java

토큰 생성 클래스.

아래 클래스 안에 정의된 메서드는 다음과 같다.

  • 시크릿 키를 생성, 디코딩 (서명 유효성 검증)
  • 엑세스 토큰 생성 (로그인 할 때 이용)
  • 엑세스 + 리프레시 토큰 생성
  • JWT 문자열을 만드는 메서드 (claim에 사용자 정보)
  • 토큰의 유효 시간을 결정 함수 (디바이스 타입별)
@Component
@RequiredArgsConstructor
public class TokenGenerator {

	private final JwtConfigProperties configProperties;
    
	private volatile SecretKey secretKey;
    
    private SecretKey getSecretKey() {
    	if (secretKey == null) { 
            synchronized (this) { 
                if (secretKey == null) {
                    secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(configProperties.getSecretKey()));
                }
            }
        }
        return secretKey;
    }
    
	public TokenDto.AccessToken generateAccessToken(String userId, String deviceType) {
        TokenDto.JwtToken jwtToken = this.generateToken(userId, deviceType, false);
        return new TokenDto.AccessToken(jwtToken);
    }

	public TokenDto.AccessRefreshToken generateAccessRefreshToken(String userId, String deviceType) {
        TokenDto.JwtToken accessJwtToken = this.generateToken(userId, deviceType, false);
        TokenDto.JwtToken refreshJwtToken = this.generateToken(userId, deviceType, true);
        return new TokenDto.AccessRefreshToken(accessJwtToken, refreshJwtToken);
    }    
    
    
	public TokenDto.JwtToken generateToken(String userId, String deviceType, boolean refreshToken) {
        int tokenExpiresIn = tokenExpiresIn(refreshToken, deviceType);
        String tokenType = refreshToken ? "refresh" : "access";

        String token = Jwts.builder()
                .issuer("welab")
                .subject(userId)
                .claim("userId", userId)
                .claim("deviceType", deviceType)
                .claim("tokenType", tokenType)
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() + tokenExpiresIn * 1000L)) 
                .signWith(getSecretKey()) 
                .header().add("typ", "JWT")
                .and()
                .compact(); 
        

        return new TokenDto.JwtToken(token, tokenExpiresIn);
    }


이 밖에도 검증하는 메서드가 있는데 그 코드는 생략했다.

Access Token, Refresh Token 차이


항목Access TokenRefresh Token
용도API 접근 인증새로운 Access Token 발급
저장 위치주로 클라이언트 메모리HttpOnly 쿠키 / DB
유효기간짧음 (예: 1일)김 (예: 1년)
보관 방식클라이언트 측서버/DB 저장 권장
보안 위험탈취 시 곧바로 API 접근 가능탈취 시 새 토큰 발급 위험

결론

현재 코드는 게이트웨이 jwt적용 전이다. 후에 게이트웨이를 적용하고 나면, 게이트웨이에서 JWT 유효성 검증, 서명 확인, 토큰 만료 확인, 사용자 정보 추출 등을 담당하게 된다.

profile
파랑입니다.

0개의 댓글