hyeonga_code

PreProject_9_JWT 서비스 구현하기(TestCode 작성) 본문

Project_HYEONGARL

PreProject_9_JWT 서비스 구현하기(TestCode 작성)

hyeonga 2024. 5. 27. 05:59
반응형

 

 

 

build.gradle

dependencies {
    // JWT
    implementation "io.jsonwebtoken:jjwt:0.9.1"
    // XML 문서와 자바 객체간 매핑 자동화
    implementation 'javax.xml.bind:jaxb-api:2.3.1'
    // Spring Security for SimpleGrantedAuthority
    implementation 'org.springframework.boot:spring-boot-starter-security:2.7.17'
}

 

+ build.gradle 전체 코드

더보기
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.5'
    id 'io.spring.dependency-management' version '1.1.4'
    id "io.freefair.lombok" version "8.6"
}

group = 'com.hyeongarl'
version = '1.0-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-web-services'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // JPA
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    // MySQL JPA
    runtimeOnly 'com.mysql:mysql-connector-j'

    // JWT
    implementation "io.jsonwebtoken:jjwt:0.9.1"
    // XML 문서와 자바 객체간 매핑 자동화
    implementation 'javax.xml.bind:jaxb-api:2.3.1'
    // Spring Security for SimpleGrantedAuthority
    implementation 'org.springframework.boot:spring-boot-starter-security:2.7.17'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

application.yml

#토큰 제공자 추가
jwt:
  issuer: ${JWT_ISSUER_NAME}
  secret_key: ${JWT_SECRET_KEY}

 

값을 application.properties에 설정해두었다.

 

 

JWT 설정 파일 

 

JwtProperties

package com.hyeongarl.config.jwt;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Getter
@Setter
@Component
@ConfigurationProperties("jwt") // Java 클래스에 프로퍼티 값을 가져와 사용
public class JwtProperties {
    private String issuer;
    private String secretKey;
}

application.yml에 지정한 값을 가지고 오는 것 같다.

 

TokenProvider

package com.hyeongarl.config.jwt;

import com.hyeongarl.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.Set;

@RequiredArgsConstructor
@Service
public class TokenProvider {
    private final JwtProperties jwtProperties;

    public String generatedToken(User user, Duration expiredAt) {
        Date now = new Date();
        return makeToken(new Date(now.getTime() + expiredAt.toMillis()), user);
    } //end generatedToken()

    // JWT 토큰 생성 메소드
    private String makeToken(Date expiry, User user) {
        Date now = new Date();

        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)   // 헤더 typ: JWT
                .setIssuer(jwtProperties.getIssuer())           // properties에서 설정한 issuer
                .setIssuedAt(now)                               // iat : 현재 시간
                .setExpiration(expiry)                          // exp : expiry 값
                .setSubject(user.getUserEmail())                // sub : 사용자 이메일
                .claim("id", user.getUserId())               // 클레임 id : 사용자 id
                // 서명 : 비밀값, 해시값을 HS256방식으로 암호화
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())   
                .compact();
    } //end makeToken()
    
    // JWT 토큰 유효성 검증 메소드
    public boolean validatedToken(String token) {
        try {  
            Jwts.parser()
                    .setSigningKey(jwtProperties.getSecretKey())    // 비밀값으로 복호화
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {     // 복호화 과정 중 에러 발싱 시 유효하지 않은 토큰
            return false;
        }
    } //end validToken()

    // 토큰 기반 인증 정보 가져오는 메소드
    public Authentication getAuthentication(String token) {
        Claims claims = getClaims(token);
        Set<SimpleGrantedAuthority> authorities 
        	= Set.of(new SimpleGrantedAuthority("ROLE_USER"));

        return new UsernamePasswordAuthenticationToken(
                new org.springframework.security.core.userdetails.User(
                		claims.getSubject(), "", authorities), token, authorities);
    } //end getAuthentication()

    // 토큰 기반 사용자 ID 가져오는 메소드
    public Long getUserId(String token) {
        Claims claims = getClaims(token);
        return claims.get("id", Long.class);
    } //end getUserId()

    private Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody();
    } //end getClaims()
}

 

 

User

package com.hyeongarl.entity;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Table(name="users")
@NoArgsConstructor
@Getter
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="user_id", updatable = false)
    private Long userId;

    @Column(name = "user_email", nullable = false, unique = true)
    private String userEmail;

    @Column(name = "user_password")
    private String password;

    @Builder
    public User(String userEmail, String password) {
        this.userEmail = userEmail;
        this.password = password;
    } //end User()
}

 

 

 

JWT Test Code 작성

 

application.properties

JWT_ISSUER_NAME=test@gmail.com
JWT_SECRET_KEY=testSecretKey

 

application.yml

#토큰 제공자 추가
jwt:
  issuer: ${JWT_ISSUER_NAME}
  secret_key: ${JWT_SECRET_KEY}

 

JwtFactory

package com.hyeongarl.config.jwt;

import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Builder;
import lombok.Getter;

import java.time.Duration;
import java.util.Date;
import java.util.Map;

import static java.util.Collections.emptyMap;

/**
 * JWT 토큰 서비스를 테스트하는데 사용할 모킹용 객체
 */
@Getter
public class JwtFactory {
    private String subject = "test@email.com";
    private Date issuedAt = new Date();
    private Date expiration = new Date(new Date().getTime() + Duration.ofDays(14).toMillis());
    private Map<String, Object> claims = emptyMap();

    @Builder
    public JwtFactory(String subject, Date issuedAt, Date expiration, Map<String, Object> claims) {
        this.subject = subject != null ? subject : this.subject;
        this.issuedAt = issuedAt != null ? issuedAt : this.issuedAt;
        this.expiration = expiration != null ? expiration : this.expiration;
        this.claims = claims != null ? claims : this.claims;
    }

    public static JwtFactory withDefaultValues() {
        return JwtFactory.builder().build();
    }

    public String createToken(JwtProperties jwtProperties) {
        return Jwts.builder()
                .setSubject(subject)
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)
                .setIssuer(jwtProperties.getIssuer())
                .setIssuedAt(issuedAt)
                .setExpiration(expiration)
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey())
                .compact();
    }
}

 

 

TokenProviderTest

package com.hyeongarl.config.jwt;

import com.hyeongarl.entity.User;
import com.hyeongarl.repository.UserRepository;
import io.jsonwebtoken.Jwts;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.Duration;
import java.util.Date;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * TokenProvider 클래스 테스트
 */
@SpringBootTest
public class TokenProviderTest {
    @Autowired
    private TokenProvider tokenProvider;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private JwtProperties jwtProperties;

    @Test
    @DisplayName("generateToken()")
    void generateTokenTest() {
        User testUser = userRepository.save(User.builder()
                .userEmail("user@email.com")
                .password("1234")
                .build());

        String token = tokenProvider.generatedToken(testUser, Duration.ofDays(14));

        Long userId = Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey())
                .parseClaimsJws(token)
                .getBody()
                .get("id", Long.class);

        assertThat(userId).isEqualTo(testUser.getUserId());
    } //end generateTokenTest()

    @Test
    @DisplayName("validateToken()")
    void validateToken_invalidTest() {
        String token = JwtFactory.builder()
                .expiration(new Date(new Date().getTime() - Duration.ofDays(7).toMillis()))
                .build()
                .createToken(jwtProperties);

        boolean result = tokenProvider.validatedToken(token);

        assertThat(result).isFalse();
    } //end validateToken_invalidTest()

    @Test
    @DisplayName("validateToken()")
    void validateToken_validTest() {
        String token = JwtFactory.withDefaultValues().createToken(jwtProperties);

        boolean result = tokenProvider.validatedToken(token);

        assertThat(result).isTrue();
    } //end validateToken_validTest()

    @Test
    @DisplayName("getAuthentication()")
    void getAuthenticationTest() {
        String userEmail = "user@email.com";
        String token = JwtFactory.builder()
                .subject(userEmail)
                .build()
                .createToken(jwtProperties);

        Authentication authentication = tokenProvider.getAuthentication(token);

        assertThat(((UserDetails) authentication.getPrincipal()).getUsername()).isEqualTo(userEmail);
    } //end getAuthenticationTest()

    @Test
    @DisplayName("getUserId()")
    void getUserIdTest() {
        Long userId = 1L;
        String token = JwtFactory.builder()
                .claims(Map.of("id", userId))
                .build()
                .createToken(jwtProperties);

        Long userIdByToken = tokenProvider.getUserId(token);

        assertThat(userIdByToken).isEqualTo(userId);
    } //end getUserIdTest()
}

 

 

테스트 실행시 모두 실행되는 것을 확인할 수 있다.

테스트 데이터베이스에 데이터가 추가된다.

반응형