작업한 내용
JWT
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
jwt.secret.key=7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA=
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
private static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private static final long TOKEN_TIME = 60 * 60 * 1000L;
@Value("${jwt.secret.key}")
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
// header 토큰을 가져오기
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(7);
}
return null;
}
// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username)
.claim(AUTHORIZATION_KEY, role)
.setExpiration(new Date(date.getTime() + TOKEN_TIME))
.setIssuedAt(date)
.signWith(key, signatureAlgorithm)
.compact();
}
// 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
log.info("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
// 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
}
아직 뭔 소리인지,, 검증 로직 작성해야하는데 이건 또 뭔지,, 공부를 좀 더 해야할 듯 ㅜ
로그인
@Transactional(readOnly = true)
public String loginUser(LoginUserRequest loginUserRequest) {
String username = loginUserRequest.getUsername();
String password = loginUserRequest.getPassword();
User user = userRepository.findByUsername(username).orElseThrow(
() -> new IllegalArgumentException("등록된 사용자가 없습니다.")
);
if (!user.getPassword().equals(password)) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
String generatedToken = jwtUtil.createToken(user.getUsername(), user.getRole());
return generatedToken;
}
@PostMapping("/api/login")
public LoginUserResponse loginUser(@RequestBody LoginUserRequest loginUserRequest, HttpServletResponse response) {
String generatedToken = userService.loginUser(loginUserRequest);
response.addHeader(JwtUtil.AUTHORIZATION_HEADER, generatedToken);
return new LoginUserResponse(200L, "로그인 완료");
}
@Getter
public class LoginUserRequest {
private String username;
private String password;
}
@Getter
public class LoginUserResponse {
private final Long statusCode;
private final String statusMessage;
public LoginUserResponse(Long statusCode, String statusMessage) {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
}
}
피드백
패스워드 검증은 패스워드를 저장하고있는 엔티티의 역할
따라서, 엔티티 내부에 커스텀 메서드를 작성하고, 거기에서 검증을 하는게 역할과 책임 측면에서 더 맞음
->
public boolean isValidPassword(String password) {
return this.password.equals(password);
}
if (!user.isValidPassword(loginUserRequest.getPassword())) {
throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
}
테스트 완료! 작동 완료!
그리고 ADMIN으로 회원가입 했을 때 확인을 어떻게 해야하는지 모르겠음,, 토큰으로 하는 거 맞나/..? 내일 이거에 대해서 조금 더 공부해보도록 하자