본 프로젝트는 “스프링부트 3 백엔드 개발자 되기” 서적을 참고하여 진행하였음
JWT 서비스 구현
•
의존성 추가
// 자바 JWT 라이브러리
implementation 'io.jsonwebtoken:jjwt:0.9.1'
// XML 문서와 Java 객체 간 매핑 자동화
implementation 'javax.xml.bind:jaxb-api:2.3.1'
Java
복사
•
토큰 제공자 추가
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
defer-datasource-initialization: true
datasource:
url: jdbc:h2:mem:testdb
username: sa
h2:
console:
enabled: true
jwt:
issuer: test123@gmail.com
secret_key: study-springboot
Java
복사
•
해당 값들을 변수로 접근하는 데 사용할 JwtProperties 작성
package com.example.msblog.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") // 자바 클래스에 프로퍼티값 가져와서 사용하는 애너테이션
public class JwtProperties {
private String issuer;
private String secretkey;
}
Java
복사
•
토큰을 생성하고 올바른 토큰인지 유효성 검사를 하기 위해 토큰에서 필요한 정보 가져오는 클래스 작성 (TokenProvider.java)
package com.example.msblog.config.jwt;
import com.example.msblog.domain.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.Collections;
import java.util.Date;
import java.util.Set;
@RequiredArgsConstructor
@Service
public class TokenProvider {
private final JwtProperties jwtProperties;
public String generateToken(User user, Duration expiredAt) {
Date now = new Date();
return makeToken(new Date(now.getTime() + expiredAt.toMillis()), user);
}
// JWT 토큰 생성 메서드
private String makeToken(Date expiry, User user) {
Date now = new Date();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // 헤더 typ : JWT
// 내용 iss : test123@gmail.com(yml 파일에서 설정한 값)
.setIssuer(jwtProperties.getIssuer())
.setIssuedAt(now) // 내용 iat : 현재 시간
.setExpiration(expiry) // 내용 exp : expiry 멤버 변수값
.setSubject(user.getEmail()) // 내용 sub : 유저 이메일
.claim("id", user.getId()) // 클레임 id : 유저 ID
// 서명 : 비밀값과 함께 해시값을 HS256 방식으로 암호화
.signWith(SignatureAlgorithm.ES256, jwtProperties.getSecretkey())
.compact();
}
// JWT 토큰 유효성 검증 메서드
public boolean vaildToken(String token) {
try {
Jwts.parser()
.setSigningKey(jwtProperties.getSecretkey()) // 비밀값으로 복호화
.parseClaimsJws(token);
return true;
} catch (Exception e) { // 복호화 과정에서 에러가 나면 유효하지 않은 토큰
return false;
}
}
// 토큰 기반으로 인증 정보를 가져오는 메서드
public Authentication getAuthentication(String token) {
Claims claims = getClaims(token);
Set<SimpleGrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(new org.springframework.
security.core.userdetails.User(
claims.getSubject(), "", authorities), token, authorities);
}
// 토큰 기반으로 유저 ID를 가져오는 메서드
public Long getUserId(String token) {
Claims claims = getClaims(token);
return claims.get("id", Long.class);
}
private Claims getClaims(String token) {
return Jwts.parser() // 클레임 조회
.setSigningKey(jwtProperties.getSecretkey())
.parseClaimsJws(token)
.getBody();
}
}
Java
복사
•
JWT 토큰 생성 메서드
◦
토큰 생성 메서드이며 인자는 만료 시간, 유저 정보를 받는다.
◦
set 계열의 메서드를 통해 여러 값을 지정한다.
◦
헤더는 typ(타입), 내용은 iss(발급자), iat(발급일시), exp(만료일시), sub(토큰 제목)이, 클레임에는 유저 ID를 지정한다.
◦
토큰을 만들 때는 properties 파일에 선언해둔 비밀값과 함께 HS256 방식으로 암호화한ㄷ.
•
JWT 토큰 유효성 검증 메서드
◦
토큰이 유효한지 검증하는 메서드이다.
◦
properties 파일에 선언한 비밀값과 함께 토큰 복호화를 진행한다.
◦
만약 복호화 과정에서 에러가 발생하면 유효하지 않은 토큰이므로 false를 반환하고 아무 에러도 발생하지 않으면
true를 반환한다.
•
토큰 기반으로 인증 정보를 가져오는 메서드
◦
토큰을 받아 인증 정보를 담은 객체 Authentication을 반환하는 메서드이다.
◦
properties 파일에 저장한 비밀값으로 토큰을 복호화한 뒤 클레임을 가져오는 private 메서드인 getClaims()를 호출해서
클레임 정보를 반환받아 사용자 이메일이 들어 있는 토큰 제목 sub와 토큰 기반으로 인증 정보를 생성한다.
이때 UsernamePasswordAuthenticationToken의 첫 인자로 들어가는 Users는 프로젝트에서 만든 User 클래스가 아닌,
스프링 시큐리티에서 제공하는 객체인 User 클래스를 임포트해야 한다.
•
토큰 기반으로 사용자 ID를 가져오는 메서드이다. properties 파일에 저장한 비밀값으로 토큰을 복호화한 다음 클레임을 가져오는
private 메서드인 getClaims()를 호출해서 클레임 정보를 반환받고 클레임에서 id 키로 저장된 값을 가져와 반환한다.
테스트 코드 작성
•
빌더 패턴을 사용하여 객체를 만들 때 테스트가 필요한 데이터만 선택하고 빌더 패턴 사용하지 않을 시 필드 기본값 사용
package com.example.msblog.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;
@Getter
public class JwtFactory {
private String subject = "test@gmail.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();
}
// jjwt 라이브러리를 사용해 JWT 토큰 생성
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.ES256, jwtProperties.getSecretkey())
.compact();
}
}
Java
복사