[Spring] Spring Security + JWT 로그인 구현 해보기 - 4 (refresh token 구현)

이혜지·2022년 3월 22일
0

Spring Security + JWT

목록 보기
5/5
post-thumbnail

yml 파일 수정

먼저 yml 파일에 refresh 토큰 만료시간 세팅 해준다.

app:
  jwtSecret: jwtsigntutorialasdfasdfasdfasdfasdf
  jwtExpirationInMs: 604800000
  # 여기 리프레시 한줄 추가
  jwtRefreshExpirationInMs: 86400000

payload(DTO 작성)

Request

TokenRefreshRequest.java 파일

public class TokenRefreshRequest {
  @NotBlank
  private String refreshToken;
  public String getRefreshToken() {
    return refreshToken;
  }
  public void setRefreshToken(String refreshToken) {
    this.refreshToken = refreshToken;
  }
}

Response

기존 작성해놓았던 JwtAuthenticationResponse.java 파일에
refreshToken 변수를 추가했다.

  1. JwtAuthenticationResponse.java
@Getter @Setter
public class JwtAuthenticationResponse {

	private String accessToken;
	private String refreshToken;
	private String tokenType = "Bearer";

	public JwtAuthenticationResponse(String accessToken, String refreshToken) {
		this.accessToken = accessToken;
		this.refreshToken = refreshToken;
	}
}

그리고 refreshToken을 받아올 클래스 작성
2. TokenRefreshResponse.java

@Getter @Setter
public class TokenRefreshResponse {
	private String accessToken;
	private String refreshToken;
	private String tokenType = "Bearer";

	public TokenRefreshResponse(String accessToken, String refreshToken) {
		this.accessToken = accessToken;
		this.refreshToken = refreshToken;
	}
}

controller 작성

기존 작성해놓았던 AuthController.java파일에 refreshToken 로직을 추가해준다.

@RestController
@RequestMapping("/api/auth")
public class AuthController {

	@Autowired
	AuthenticationManager authenticationManager;

	@Autowired
	RefreshTokenService refreshTokenService;

	@Autowired
	UserRepository userRepository;

	@Autowired
	RoleRepository roleRepository;

	@Autowired
	PasswordEncoder passwordEncoder;

	@Autowired
	JwtTokenProvider tokenProvider;


	# 로그인 부분에 RefreshToken 추가
	@PostMapping("/signin")
	public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {

		Authentication authentication = authenticationManager.authenticate(
			new UsernamePasswordAuthenticationToken(
				loginRequest.getEmail(),
				loginRequest.getPassword()
			)
		);

		SecurityContextHolder.getContext().setAuthentication(authentication);

		String jwt = tokenProvider.generateToken(authentication);
		
        #RefreshToken 로직 추가
		RefreshToken refreshToken = refreshTokenService.createRefreshToken(authentication);
		return ResponseEntity.ok(new JwtAuthenticationResponse(jwt, refreshToken.getToken()));
	}

	# RefreshToken api 작성
	@PostMapping("/refreshtoken")
	public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest tokenRefreshRequest) {
		String requestRefreshToken = tokenRefreshRequest.getRefreshToken();

		return refreshTokenService.findByToken(requestRefreshToken)
			.map(refreshTokenService::verifyExpiration)
			.map(RefreshToken::getUser)
			.map(user -> {
				Authentication authentication = authenticationManager.authenticate(
					new UsernamePasswordAuthenticationToken(
						user.getEmail(), user.getPassword()
					)
				);
				String token = tokenProvider.generateToken(authentication);
				return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));

			})
			.orElseThrow(() -> new TokenRefreshException(requestRefreshToken, "Refresh token is not in database!"));
	}

}

아래에서
Refresh Token 모델을 작성하고,
Jwt Refresh Token Service 작성할것이다.

Create Refresh Token Service

model 작성

  1. 먼저 리프레시 토큰 모델을 만들자.
    RefreshToken.java
@Getter @Setter
@Entity(name = "refreshtoken")
public class RefreshToken {
	@Id @GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;

	@OneToOne
	@JoinColumn(name = "user_id", referencedColumnName = "id")
	private User user;

	@Column(nullable = false, unique = true)
	private String token;

	@Column(nullable = false)
	private Instant expiryDate;

}

repository 작성

  1. 리프레시 토큰 레포지토리를 생성한다.
    RefreshTokenRepository.java
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {

	@Override
	Optional<RefreshToken> findById(Long id);
	Optional<RefreshToken> findByToken(String token);

	int deleteByUser(User user);
}

service 작성

  1. 그 다음 리프레시 토큰 서비스를 생성
    RefreshTokenService.java

@Service
public class RefreshTokenService {

	@Value("${app.jwtRefreshExpirationInMs}")
	private Long refreshTokenDurationMs;

	@Autowired
	private RefreshTokenRepository refreshTokenRepository;

	@Autowired
	private UserRepository userRepository;

	public Optional<RefreshToken> findByToken(String token) {
		return refreshTokenRepository.findByToken(token);
	}

	public RefreshToken createRefreshToken(Authentication authentication) {

		UserPrincipal userPrincipal = (UserPrincipal)authentication.getPrincipal();

		RefreshToken refreshToken = new RefreshToken();
		refreshToken.setUser(userRepository.findById(userPrincipal.getId()).get());
		refreshToken.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
		refreshToken.setToken(UUID.randomUUID().toString());
		refreshToken = refreshTokenRepository.save(refreshToken);
		return refreshToken;
	}

	public RefreshToken verifyExpiration(RefreshToken token) {
		if (token.getExpiryDate().compareTo(Instant.now()) < 0) {
			refreshTokenRepository.delete(token);
			throw new TokenRefreshException(token.getToken(), "Refresh token was expired. Please make a new signin request");
		}
		return token;
	}

	@Transactional
	public int deleteByUserId(Long userId) {
		return refreshTokenRepository.deleteByUser(userRepository.findById(userId).get());
	}
}

예외처리 Exception 작성

TokenRefreshException.java

@ResponseStatus(HttpStatus.FORBIDDEN)
@Getter
@Setter
public class TokenRefreshException extends RuntimeException {

	private static final long serialVersionUID = 1L;
	public TokenRefreshException(String token, String message) {
		super(String.format("Failed for [%s]: %s", token, message));
	}
}
profile
공유 문화를 지향하는 개발자입니다.

2개의 댓글

comment-user-thumbnail
2022년 8월 10일

controller에 refreshToken 로직을 추가하는 이유가 있을까요?

1개의 답글