이제 본격적으로 비즈니스 로직을 작성할 것이다.
모든 코드는 프로그래머스 웹 백엔드 스터디 강의 중 받았던 샘플 코드를 바탕으로 작성되었다.
package com.august.soil.api.xxx;
august
는 팀 이름, soil
은 프로젝트 이름이다. 그 아래에 api
패키지를 생성했다. API 관련 비즈니스 코드들은 모두 api
아래에 생성되어 있다.package com.august.soil.api.security;
import
될 수 있게 security
패키지를 생성한다.JWT
와 Spring Security
관련 로직들을 작성할 것이다.security
패키지 아래에 Jwt
라는 이름으로 클래스를 생성한다. import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
public class Jwt {
private final String issuer;
private final String clientSecret;
private final int expirySeconds;
private final Algorithm algorithm;
private final JWTVerifier jwtVerifier;
}
클래스 내의 멤버변수를 선언할 때엔 기본적으로
final
키워드를 붙여 초기화 이후 변경이 불가능하게 만든다.
이후 변경이 필요한 멤버변수가 있으면 그것만 제한적으로final
키워드를 제거하고setter
를 만드는 형태로 코드를 작성하는 등 불필요한 상태 변화를 방지하도록 한다.
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
public class Jwt {
...
public Jwt(String issuer, String clientSecret, int expirySeconds) {
this.issuer = issuer;
this.clientSecret = clientSecret;
this.expirySeconds = expirySeconds;
this.algorithm = Algorithm.HMAC512(clientSecret);
this.jwtVerifier = JWT.require(algorithm)
.withIssuer(issuer)
.build();
}
}
final
키워드를 붙여주었기 때문에 생성자를 통해서 초기화 해야한다. 의존성으로 롬복을 사용하기 때문에 @RequiredArgsContructor
를 사용하면 생성자 코드를 일일이 작성할 필요는 없지만 이 클래스에서 일부 멤버변수는 암호화 처리와 함께 초기화 할 것이라서 따로 만들었다.import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.Getter;
import java.util.Date;
@Getter
public class Jwt {
...
public String newToken(Claims claims) {
Date now = new Date();
JWTCreator.Builder builder = JWT.create();
builder.withIssuer(issuer);
builder.withIssuedAt(now);
if (expirySeconds > 0) {
builder.withExpiresAt(new Date(now.getTime() + expirySeconds * 1_000L));
}
builder.withClaim("userKey", claims.userKey);
builder.withClaim("name", claims.name);
builder.withClaim("email", claims.email.getEmail());
builder.withArrayClaim("roles", claims.roles);
return "Bearer " + builder.sign(algorithm);
}
}
getter
는 롬복 어노테이션으로 해결했다.import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.Getter;
import java.util.Date;
@Getter
public class Jwt {
...
public String refreshToken(String token) throws JWTVerificationException {
Claims claims = verify(token);
claims.eraseIat();
claims.eraseExp();
return newToken(claims);
}
public Claims verify(String token) throws JWTVerificationException {
return new Claims(jwtVerifier.verify(token));
}
}
Claims
는 이 클래스 내부에 스태틱 클래스로 만들 것이다.import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.Date;
@Getter
public class Jwt {
...
@NoArgsConstructor
static public class Claims {
Long userKey;
String name;
Email email;
String[] roles;
Date iat;
Date exp;
Claims(DecodedJWT decodedJWT) {
Claim userKey = decodedJWT.getClaim("userKey");
if (!userKey.isNull())
this.userKey = userKey.asLong();
Claim name = decodedJWT.getClaim("name");
if (!name.isNull())
this.name = name.asString();
Claim email = decodedJWT.getClaim("email");
if (!email.isNull())
this.email = new Email(email.asString());
Claim roles = decodedJWT.getClaim("roles");
if (!roles.isNull())
this.roles = roles.asArray(String.class);
this.iat = decodedJWT.getIssuedAt();
this.exp = decodedJWT.getExpiresAt();
}
public static Claims of(long userKey, String name, Email email, String[] roles) {
Claims claims = new Claims();
claims.userKey = userKey;
claims.name = name;
claims.email = email;
claims.roles = roles;
return claims;
}
long iat() {
return iat != null ? iat.getTime() : -1;
}
long exp() {
return exp != null ? exp.getTime() : -1;
}
void eraseIat() {
iat = null;
}
void eraseExp() {
exp = null;
}
}
}
JWT
토큰을 만드는 데에 필요한 정보들을 담은 클래스 Claim
을 만든다. 클래스 내부에서만 사용될 것이기 때문에 새로운 클래스 파일로 만들지는 않았다. claim
을 JWTCreator
로 넘기면 JWT
토큰을 발급해 준다.import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import lombok.Getter;
import java.util.Date;
import org.apache.commons.lang3.builder.ToStringStyle;
import static org.apache.commons.lang3.builder.ToStringBuilder.reflectionToString;
@Getter
public class Jwt {
...
@Override
public String toString() {
return reflectionToString(this, ToStringStyle.JSON_STYLE);
}
}
toString()
을 오버라이딩 해 주었는데 손 아프게 쓰는 것 좀 줄여보려고 org.apache.commons.lang3
패키지의 ToStringBuilder
를 사용했다. 이것만 해도 단순노동이 많이 줄어든다.
ToStringBuilder
란?
https://velog.io/@miro7923/JAVA-ToStringBuilder-사용이유와-사용법
ToStringBuilder
가 생소하다면 위 게시글을 참고하자. @Configuration
public class ServiceConfigure {
@Bean
public Jwt jwt(JwtTokenConfigure jwtTokenConfigure) {
return new Jwt(jwtTokenConfigure.getIssuer(), jwtTokenConfigure.getClientSecret(), jwtTokenConfigure.getExpirySeconds());
}
}
Service layer
와 관련된 설정 정보를 처리하는 클래스에 빈으로 등록한다. Jwt
클래스의 생성이 완료된다. https://github.com/miro7923/soil/blob/main/soil/src/main/java/com/august/soil/api/security/Jwt.java
Github
에서도 볼 수 있다. JWT
토큰 발급 클래스와 관련해 상세하게 설명이 되어 있다. 토큰 발급 과정을 보다 이해하기 위해 참고하면 좋은 글이라 링크를 추가한다.
다음부터 오늘 만든 Jwt
객체를 이용해 토큰을 발급하는 클래스를 만들 것이다.