Spring Spring Security+JWTログイン-4の実装(リフレッシュトークンの実装)


ymlファイルの変更


まずymlファイルでrefreshタグの有効期限を設定します.
app:
  jwtSecret: jwtsigntutorialasdfasdfasdfasdfasdf
  jwtExpirationInMs: 604800000
  # 여기 리프레시 한줄 추가
  jwtRefreshExpirationInMs: 86400000

ペイロード(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変数が追加されました.
  • 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;
    	}
    }

    コントローラの作成


    既存の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モデルを作成します.
    JWTRefresh Token Serviceが作成されます.

    Create Refresh Token Service


    モデルの作成

  • まずRifreshTokenモデルを作ります.
    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;
    
    }

    リポジトリの作成

  • リプレシト大レジストリを生成します.
    RefreshTokenRepository.java
  • public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
    
    	@Override
    	Optional<RefreshToken> findById(Long id);
    	Optional<RefreshToken> findByToken(String token);
    
    	int deleteByUser(User user);
    }

    サービスの作成

  • その後、リフレッシュトークンサービスが作成される
    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));
    	}
    }