Skip to content

Commit

Permalink
FEAT: (#47) Refresh Token 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
anxi01 committed Jul 2, 2024
1 parent a44c19c commit adcf9fd
Show file tree
Hide file tree
Showing 33 changed files with 234 additions and 67 deletions.
3 changes: 2 additions & 1 deletion src/main/java/com/zerozero/auth/AuthenticationResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
@NoArgsConstructor
public class AuthenticationResponse {

private String token;
private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.zerozero.auth;

import com.zerozero.core.exception.error.ErrorCode;
import com.zerozero.core.exception.ServiceException;
import com.zerozero.core.exception.error.ErrorCode;

public class DuplicateEmailException extends ServiceException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.zerozero.auth;

import com.zerozero.core.exception.error.ErrorCode;
import com.zerozero.core.exception.ServiceException;
import com.zerozero.core.exception.error.ErrorCode;

public class DuplicateNicknameException extends ServiceException {

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/zerozero/auth/InvalidTokenException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zerozero.auth;

import com.zerozero.core.exception.ServiceException;
import com.zerozero.core.exception.error.ErrorCode;

public class InvalidTokenException extends ServiceException {

public InvalidTokenException() {
super(ErrorCode.INVALID_TOKEN_EXCEPTION);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/zerozero/auth/TokenDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.zerozero.auth;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class TokenDto {

private String accessToken;
private String refreshToken;
}
9 changes: 9 additions & 0 deletions src/main/java/com/zerozero/auth/TokenRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.zerozero.auth;

import lombok.Getter;

@Getter
public class TokenRequest {

private String refreshToken;
}
2 changes: 1 addition & 1 deletion src/main/java/com/zerozero/auth/UserNotFoundException.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.zerozero.auth;

import com.zerozero.core.exception.error.ErrorCode;
import com.zerozero.core.exception.ServiceException;
import com.zerozero.core.exception.error.ErrorCode;

public class UserNotFoundException extends ServiceException {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.zerozero.auth.application;

import com.zerozero.auth.AuthenticationRequest;
import com.zerozero.user.Role;
import com.zerozero.core.domain.entity.User;
import com.zerozero.core.domain.infra.repository.UserJPARepository;
import com.zerozero.auth.RegisterRequest;
import com.zerozero.auth.AuthenticationResponse;
import com.zerozero.auth.DuplicateEmailException;
import com.zerozero.auth.DuplicateNicknameException;
import com.zerozero.auth.InvalidTokenException;
import com.zerozero.auth.RegisterRequest;
import com.zerozero.auth.TokenDto;
import com.zerozero.auth.UserNotFoundException;
import com.zerozero.core.domain.entity.RefreshToken;
import com.zerozero.core.domain.entity.User;
import com.zerozero.core.domain.infra.repository.RefreshTokenRepository;
import com.zerozero.core.domain.infra.repository.UserJPARepository;
import com.zerozero.core.util.JwtService;
import com.zerozero.user.Role;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
Expand All @@ -24,6 +29,7 @@ public class AuthenticationService {
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
private final RefreshTokenRepository refreshTokenRepository;

public AuthenticationResponse register(RegisterRequest request) {
User user = User.builder()
Expand All @@ -34,10 +40,11 @@ public AuthenticationResponse register(RegisterRequest request) {
.build();

userJPARepository.save(user);
String jwtToken = jwtService.generateToken(user);
TokenDto tokens = jwtService.generateToken(user);

return AuthenticationResponse.builder()
.token(jwtToken)
.accessToken(tokens.getAccessToken())
.refreshToken(tokens.getRefreshToken())
.build();
}

Expand All @@ -46,10 +53,20 @@ public AuthenticationResponse authenticate(AuthenticationRequest request) {
new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));

User user = userJPARepository.findByEmail(request.getEmail()).orElseThrow(UserNotFoundException::new);
String jwtToken = jwtService.generateToken(user);
TokenDto tokens = jwtService.generateToken(user);

Optional<RefreshToken> refreshToken = refreshTokenRepository.findByUser(user);

if (refreshToken.isPresent()) {
refreshTokenRepository.save(refreshToken.get().update(tokens.getRefreshToken()));
} else {
RefreshToken newRefreshToken = new RefreshToken(tokens.getRefreshToken(), user);
refreshTokenRepository.save(newRefreshToken);
}

return AuthenticationResponse.builder()
.token(jwtToken)
.accessToken(tokens.getAccessToken())
.refreshToken(tokens.getRefreshToken())
.build();
}

Expand All @@ -64,4 +81,22 @@ public void checkNickname(String nickname) {
throw new DuplicateNicknameException();
}
}

public AuthenticationResponse refreshToken(String refreshToken) {

String userEmail = jwtService.extractUsername(refreshToken);
if (userEmail != null) {
User user = userJPARepository.findByEmail(userEmail).orElseThrow(UserNotFoundException::new);
String storedRefreshToken = refreshTokenRepository.findByUser(user).orElseThrow(
InvalidTokenException::new).getRefreshToken();
if (storedRefreshToken.equals(refreshToken) && jwtService.isTokenValid(refreshToken, user)) {
TokenDto tokens = jwtService.generateToken(user);
return AuthenticationResponse.builder()
.accessToken(tokens.getAccessToken())
.refreshToken(refreshToken)
.build();
}
}
throw new InvalidTokenException();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.zerozero.auth.presentation;

import com.zerozero.auth.application.AuthenticationService;
import com.zerozero.auth.AuthenticationRequest;
import com.zerozero.auth.RegisterRequest;
import com.zerozero.auth.AuthenticationResponse;
import com.zerozero.auth.RegisterRequest;
import com.zerozero.auth.TokenRequest;
import com.zerozero.auth.application.AuthenticationService;
import com.zerozero.core.presentation.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -49,4 +50,10 @@ public ApiResponse<String> checkNickname(@PathVariable String nickname) {
service.checkNickname(nickname);
return ApiResponse.ok();
}

@Operation(summary = "리프레시 토큰", description = "액세스 토큰 만료시, 리프레시 토큰을 이용해 액세스 토큰을 재발급합니다.")
@PostMapping("/refresh-token")
public ApiResponse<AuthenticationResponse> refreshToken(@RequestBody TokenRequest request) {
return ApiResponse.ok(service.refreshToken(request.getRefreshToken()));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.zerozero.configuration.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zerozero.core.exception.ServiceException;
import com.zerozero.core.exception.error.ErrorCode;
import com.zerozero.core.presentation.ErrorResponse;
import com.zerozero.core.exception.ServiceException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -32,7 +32,7 @@ private void sendErrorResponse(HttpServletResponse response, ErrorCode errorCode
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
ObjectMapper objectMapper = new ObjectMapper();
ErrorResponse errorResponse = ErrorResponse.of(errorCode);
ErrorResponse errorResponse = ErrorResponse.from(errorCode);
Map<String, ErrorResponse> result = new HashMap<>();
result.put("result", errorResponse);
response.getWriter().write(objectMapper.writeValueAsString(result));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

if (request.getServletPath().contains("/api/v1/auth")) {
filterChain.doFilter(request, response);
return;
}

final String authHeader = request.getHeader("Authorization");
final String jwt;
final String userEmail;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.zerozero.configuration.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zerozero.core.exception.error.ErrorCode;
import com.zerozero.core.presentation.ErrorResponse;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.filter.OncePerRequestFilter;

public class JwtExceptionFilter extends OncePerRequestFilter {
Expand All @@ -18,13 +22,18 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
try {
filterChain.doFilter(request, response);
} catch (JwtException e) {
sendErrorResponse(HttpStatus.UNAUTHORIZED, response, e);
sendErrorResponse(response, ErrorCode.EXPIRED_JWT_EXCEPTION);
}
}

private void sendErrorResponse(HttpStatus status, HttpServletResponse response, Exception e) throws IOException {
response.setStatus(status.value());
private void sendErrorResponse(HttpServletResponse response, ErrorCode errorCode) throws IOException {
response.setStatus(errorCode.getHttpStatus());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write("{\"error\": \"" + e.getMessage() + "\"}");
ObjectMapper objectMapper = new ObjectMapper();
ErrorResponse errorResponse = ErrorResponse.from(errorCode);
Map<String, ErrorResponse> result = new HashMap<>();
result.put("result", errorResponse);
response.getWriter().write(objectMapper.writeValueAsString(result));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.zerozero.configuration.security;

import com.zerozero.core.domain.infra.repository.UserJPARepository;
import com.zerozero.auth.UserNotFoundException;
import com.zerozero.core.domain.infra.repository.UserJPARepository;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/com/zerozero/core/domain/entity/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.zerozero.core.domain.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class RefreshToken {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String refreshToken;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;

public RefreshToken(String refreshToken, User user) {
this.refreshToken = refreshToken;
this.user = user;
}

public RefreshToken update(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/zerozero/core/domain/entity/Review.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.zerozero.core.domain.entity;

import com.zerozero.review.ZeroDrinks;
import com.zerozero.review.ReviewRequest;
import com.zerozero.core.domain.shared.BaseEntity;
import com.zerozero.review.ReviewRequest;
import com.zerozero.review.ZeroDrinks;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/zerozero/core/domain/entity/Store.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.zerozero.core.domain.entity;

import com.zerozero.external.naver.SearchLocalResponse.SearchLocalItem;
import com.zerozero.core.domain.shared.BaseEntity;
import com.zerozero.external.naver.SearchLocalResponse.SearchLocalItem;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.zerozero.core.domain.infra.repository;

import com.zerozero.core.domain.entity.RefreshToken;
import com.zerozero.core.domain.entity.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class ExceptionManager {

@ExceptionHandler(ServiceException.class)
public ApiResponse<?> handleException(ServiceException e) {
ErrorResponse errorResponse = ErrorResponse.of(e.getErrorCode());
ErrorResponse errorResponse = ErrorResponse.from(e.getErrorCode());
return ApiResponse.fail(errorResponse, errorResponse.getCode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ public enum ErrorCode {
STORE_NOT_FOUND_EXCEPTION(404, "STORE_NOT_FOUND_EXCEPTION", "판매점이 존재하지 않습니다."),
REVIEW_NOT_FOUND_EXCEPTION(404, "REVIEW_NOT_FOUND_EXCEPTION", "리뷰가 존재하지 않습니다."),
ACCESS_DENIED_EXCEPTION(500, "ACCESS_DENIED_EXCEPTION", "권한이 없습니다."),
ALREADY_REVIEWED_EXCEPTION(409, "ALREADY_REVIEWED_EXCEPTION", "해당 판매점에 리뷰가 이미 등록돼있습니다.");
ALREADY_REVIEWED_EXCEPTION(409, "ALREADY_REVIEWED_EXCEPTION", "해당 판매점에 리뷰가 이미 등록돼있습니다."),
INVALID_TOKEN_EXCEPTION(404, "INVALID_TOKEN_EXCEPTION", "유효하지 않은 토큰입니다."),
EXPIRED_JWT_EXCEPTION(401, "EXPIRED_JWT_EXCEPTION", "토큰이 만료되었습니다.");

private final int httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public ErrorResponse(ErrorCode errorCode) {
this.code = errorCode.getCode();
}

public static ErrorResponse of(ErrorCode errorCode) {
public static ErrorResponse from(ErrorCode errorCode) {
return new ErrorResponse(errorCode);
}
}
Loading

0 comments on commit adcf9fd

Please sign in to comment.