🔥스파르타 TIL (Spring)

JWT 활용하기 (1)

승승장규 2025. 2. 10. 00:03

인증(Authentication)과 인가(Authorization) 란?

 

인증(Authentication)과 인가(Authorization) 란?

인증 ▼해당 유저가 실제 유저인지 인증하는 개념지문인식, 로그인 등 실제 유저가 맞는지 확인하는 장치인가 ▼해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념관리자 페이

seungg8361.tistory.com

 

JWT를 사용하기 전에 의존성과 설정을  추가해 주자

 

build.gradle

compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

 

application.properties

jwt.secret.key={비밀 키 설정} // ex) base64 인코딩이 되어있는 비밀 키

 

먼저 사용자 권한을 관리하기 위한 enum을 생성해 보자

// enum은 상수의 집합 클래스
public enum UserRoleEnum {

    // 각 상수의 열거형 인스턴스
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    // ex) UserRoleEnum.USER.getAuthority -> "ROLE_USER"를 반환한다.
    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

 

 

JWT를 사용하는 단계 ▼

 JwtUtil.java 파일을 생성해서 관리해 보자.

 

0. JWT 데이터 

    // Header KEY 값 ex) 쿠키의 name
    public static final String AUTHORIZATION_HEADER = "Authorization";
    // 사용자 권한 값의 KEY ex) admin, 일반 유저
    public static final String AUTHORIZATION_KEY = "auth";
    // Token 식별자 : 토큰 앞에 붙음 => 해당하는 값은 토큰이다 라는 규칙
    // Bearer는 JWT나 Oauth에 대한 토큰을 사용한다는 뜻.
    public static final String BEARER_PREFIX = "Bearer ";
    
    private final long TOKEN_TIME = 60 * 60 * 1000L; // 토큰 만료 시간 ex) 60분
    // application.properties에 설정한 값을 가져온다
    @Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
    private String secretKey;
    
    private Key key; // secret 키를 담을 객체
    // enum으로 되어있음.
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    // 딱 한 번만 받아오면 되는 값을 사용할 때마다 요청을 새로 받아오는 것을 막기위해
    // JwtUtil 클래스의 생성자 호출한 뒤에 key필드에 secretKey를 담기위해
    @PostConstruct
    public void init() {
        // base64로 인코딩 되어 있는 secretKey를 디코딩해서 사용
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        // HMAC 알고리즘에 적합한 키를 생성 ex) HS256, HS384, HS512와 같은 표준 HMAC 서명 알고리즘에 사용
        key = Keys.hmacShaKeyFor(bytes);
    }

 

1. JWT 생성

public String createToken(String username, UserRoleEnum role) {
        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder() // Jwts라는 클래스에 빌더를 사용 ex) JWT 토큰이 생성이 된다.
                        .setSubject(username) // 사용자 식별자 값(ID)
                        .claim(AUTHORIZATION_KEY, role) // 사용자 권한 (앞에는 key, 뒤에는 value)
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
                        .setIssuedAt(date) // 발급일
                        // 앞에는 secretKey값, 뒤에는 암호화 알고리즘
                        .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                        .compact();
    }

 

2. 생성된 JWT를 Cookie에 저장

 public void addJwtToCookie(String token, HttpServletResponse response) {
        try {
            // URLEncoder를 사용해서 url에서 사용되지 않는 문자들을 %와 특수기호로 변환
            // token -> 인코딩할 문자열, utf-8 -> 사용할 문자 집합
            // 쿠키에 저장하기 전에 공백제거
            token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20");

            // "Authorization" 에는 token의 값이 들어있다.
            Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // key-value
            cookie.setPath("/");

            // Response 객체에 Cookie 추가하여 브라우저로 반환하면 브라우저의 쿠키 저장소에 저장됨.
            // 개발자 모드에 Set-cookie 부분에 "Authorization=Bearer="로 저장되고 name은 Authorization으로 저장됨.
            response.addCookie(cookie);
        } catch (UnsupportedEncodingException e) {
            logger.error(e.getMessage());
        }
    }

 

3. Cookie에 들어있는 JWT 토큰을 Substring

public String substringToken(String tokenValue) {
        log.info("substringToken");
        // 공백과 null인지 Bearer로 시작 하는지 안하는지 확인
        if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
            return tokenValue.substring(7); // 순수한 토큰을 뽑아내기 위해 Bearer, 공백을 포함햔 7자 인덱싱
        }
        logger.error("Not Found Token");
        throw new NullPointerException("Not Found Token");
    }

 

4. JWT 검증

public boolean validateToken(String token) {
        try {
            // Jwts.parserBuilder를 사용하여 JWT를 파싱할 수 있음.
            // 검증, setSigningKey -> 서명 검증에 사용할 비밀키를 설정, parseClaimsJws -> 서명 검증에 성공하면 Jws<Claims> 객체 반환
            // Jws(Json Web Signature) : 서명된 JWT 토큰, Claims : Payload 부분
            Jwts.parserBuilder()
                    .setSigningKey(key).build()
                    .parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException | SignatureException e) {
            logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
        } catch (ExpiredJwtException e) {
            logger.error("Expired JWT token, 만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
        } catch (IllegalArgumentException e) {
            logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
        }
        return false;
    }

 

5. JWT에서 사용자 정보 가져오기

public Claims getUserInfoFromToken(String token){
        // Jwts.parserBuilder() 와 secretKey 를 사용하여 JWT 의 Claims 를 가져와 담겨있는 사용자의 정보를 사용
        return Jwts.parserBuilder()
                .setSigningKey(key).build()
                .parseClaimsJws(token)
                .getBody(); // claims에 있는 정보들 반환
    }

 

기타. 필터 구현 시 Cookie 값에서 JWT 가져오기

// HttpServletRequest 에서 Cookie Value : JWT 가져오기
    public String getTokenFromRequest(HttpServletRequest res){
        Cookie[] cookies = res.getCookies();
        if(cookies != null){
            for(Cookie cookie : cookies){
                if(cookie.getName().equals(AUTHORIZATION_HEADER)){
                    try{
                        return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
                    }catch(UnsupportedEncodingException e){
                        return null;
                    }
                }
            }
        }
        return null;
    }

'🔥스파르타 TIL (Spring)' 카테고리의 다른 글

Spring Security 란?  (0) 2025.02.10
필터(Filter) 란?  (0) 2025.02.10
인증(Authentication)과 인가(Authorization) 란?  (0) 2025.02.09
Bean 이란?  (0) 2025.02.09
JPA 란? (2) - Spring Data JPA  (0) 2025.02.08