SpringBoot - jwt token 발급받기

박정민·2023년 3월 29일
0

SpringBoot

목록 보기
3/3
post-thumbnail

JWT(Json Web Token)

JWT(Json Web Token)란 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다.

JWT 구조

JWT는 Header, Payload, Signature의 3 부분으로 이루어지며, Json 형태인 각 부분은 Base64Url로 인코딩 되어 표현된다. 또한 각각의 부분을 이어 주기 위해 . 구분자를 사용하여 구분한다. 추가로 Base64Url는 암호화된 문자열이 아니고, 같은 문자열에 대해 항상 같은 인코딩 문자열을 반환한다.

토큰의 헤더는 typ과 alg 두 가지 정보로 구성된다. alg는 헤더(Header)를 암호화 하는 것이 아니고, Signature를 해싱하기 위한 알고리즘을 지정하는 것이다.

  • typ: 토큰의 타입을 지정 ex) JWT
  • alg: 알고리즘 방식을 지정하며, 서명(Signature) 및 토큰 검증에 사용 ex) HS256(SHA256) 또는 RSA

PayLoad (Data)

토큰의 페이로드에는 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨 있다.

  • 발급자
  • 권한
  • 발행시간
  • 만료시간

등등 내용을 담아서 보낸다

Signature(서명)

서명(Signature)은 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다. 서명(Signature)은 위에서 만든 헤더(Header)와 페이로드(Payload)의 값을 각각 BASE64Url로 인코딩하고, 인코딩한 값을 비밀 키를 이용해 헤더(Header)에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64Url로 인코딩하여 생성한다.

Spring Boot로 JWT 발급받기

build.gradle

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	implementation 'org.springframework.boot:spring-boot-starter:3.0.5'
}

스프링 시큐리티는 추후 필터체인을 위해 추가하였다. 단순 JWT 토큰 발급만 원한다면 지워도 좋다.

파일 구조

Config > SecurityConfig

@Bean
    protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //csrf 보안 토큰 비활성화, JWT 토큰을 사용할경우 필수적으로 사용
        http.csrf().disable();
        http.httpBasic().disable();
        http.authorizeHttpRequests().requestMatchers("/*").permitAll();
        
                /*
                .authorizeRequests() → .authorizeHttpRequests()
                  .antMatchers() → .requestMatchers()
                */
                
        return http.build();
    }
}

Controller > UserController

@RestController
public class UserController {

    private final Logger Logger = LogManager.getLogger(this.getClass());
    private final JwtTokenProvider jwtTokenProvider;

    public UserController(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @PostMapping("/login")
    public String login(@RequestBody userDTO userDTO ) {
        String uPK= userDTO.getId();
        String roles = "admin";
        HttpHeaders headers = new HttpHeaders();
        return new JwtTokenProvider().createToken(uPK,roles);
    }
}

DTO > userDTO

@Setter
@Getter
@Builder
@AllArgsConstructor
public class userDTO {
    String id;
    String pw;
}

Util > JwtTokenProvider

@Component
public class JwtTokenProvider {
    private Logger Logger = LogManager.getLogger(this.getClass());
    private String secretKey = "236979CB6F1AD6B6A6184A31E6BE37DB3818CC36871E26235DD67DCFE4041492";
    private final Key key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
    /*
    io.jsonwebtoken.security.Keys 클래스의 hmacShaKeyFor() 메서드를 이용하여,
    문자열 secretKey를 기반으로 HMAC-SHA 알고리즘에 사용될 수 있는 javax.crypto.SecretKey 객체를 생성.
     */

    // 토큰 유효시간 30분
    private final long tokenValidTime = 30 * 60 * 1000L;


    // 객체 초기화, secretKey를 Base64로 인코딩한다.
    @PostConstruct
    protected void init() {
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
    }

    // JWT 토큰 생성
    public String createToken(String userPk, String roles) {
        System.out.println(key);
        System.out.println("userPK: "+userPk);
        System.out.println(roles);
        Map<String, Object> payloads = new HashMap<>();
        payloads.put("ID", userPk);
        payloads.put("roles", roles);
        Date now = new Date();
        Logger.info("Create Token");
        return Jwts.builder()
                .setSubject("JWT AUTH")
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)
                .setClaims(payloads) // 정보 저장
                .setIssuedAt(now) // 토큰 발행 시간 정보
                .setExpiration(new Date(now.getTime() + tokenValidTime)) //만료시간 정보
                .signWith(key,SignatureAlgorithm.HS256)  // 사용할 암호화 알고리즘과 signature 에 들어갈 secret값 세팅
                .compact();
    }

    
}

담을 데이터가 ID, 권한 뿐이라 HashMap으로 바로 넣었지만, Claim 부분을 따로 구분하고 싶다면 중간부분에 아래같이 선언하여 Return하는 방법도 있다.

//Claims
Claims claims = Jwts.claims()
        .setSubject("Title")
        .setIssuedAt(now) // 토큰 발행 시간 정보
        .setExpiration(new Date(now.getTime() + tokenValidTime)) //만료시간 정보
        
        return Jwts.builder()
                .setHeaderParam(Header.TYPE, Header.JWT_TYPE)
                .setClaims(claims) // 정보 저장
                .signWith(key,SignatureAlgorithm.HS256)  // 사용할 암호화 알고리즘과 signature 에 들어갈 secret값 세팅
                .compact();

결과

성공적으로 받아온것을 확인 가능하다.

* provider의 Secret key

방금같은 경우에는 단순 JWT 토큰 발급을 위해 클래스 안에 Key를 선언하였지만, 실무 혹은 실제 프로젝트에서는 외부 환경변수처럼 따로 취급을 하고, 접근 권한을 부여해 관리하는것이 보안상 좋다

profile
Junior Backend Developer

0개의 댓글