Photo by JuniperPhoton on Unsplash
Nếu bạn chưa đọc phần 1 của bài viết thì bạn có thể đọc lại ở đây. Trong phần 2 này, mình sẽ trình bày cách để mình implement Refresh Token Rotation với Spring Boot.
Trong phần này bạn sẽ cần tạo sẵn những thành phần cần có cho Spring Boot gồm controller, service, model, repository.
Trong Controller ta sẽ cần chuẩn bị sẳn 2 router để cho User Sign In và Sign Up ngoài ra cần thêm 1 API để FE gọi khi cần dùng Refresh Token
@Slf4j
@RestController
@RequestMapping("/api")
public class AuthServiceController {
@Autowired
private AuthService authService;
@PostMapping("/v1.0/auth/sign-in")
@ResponseStatus(code = HttpStatus.OK)
public Mono<ResponseEntity<ResponseModel>> signIn(@Valid @RequestBody SignInRequestDto request) {
try {
return authService.signIn(request)
.map(response -> {
return ResponseEntity.ok(
new ResponseModel(200, ResponseMessage.SUCCESSFUL, response)
);
})
.switchIfEmpty(Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()));
} catch (Exception error) {
log.error("Can not sign in", error);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Can not auth user", error);
}
}
@PostMapping("/v1.0/auth/sign-up")
@ResponseStatus(code = HttpStatus.OK)
public Mono<ResponseEntity<ResponseModel>> signUp(@Valid @RequestBody SignUpRequestDto request) {
try {
authService.signUp(request);
return Mono.just(ResponseEntity.ok(
new ResponseModel(200, ResponseMessage.SUCCESSFUL, null)
));
} catch (Exception error) {
log.error("Can not sign in", error);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Can not auth user", error);
}
}
@PostMapping("/v1.0/auth/refresh-token")
@ResponseStatus(code = HttpStatus.OK)
public Mono<ResponseEntity<ResponseModel>> refreshToken(@RequestBody HashMap<String, Object> request) {
try {
return authService.createRefreshToken(UUID.fromString((String) request.get("refreshToken")))
.map(response -> {
return ResponseEntity.ok(new ResponseModel(200, ResponseMessage.SUCCESSFUL, response));
})
.switchIfEmpty(Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()));
} catch (Exception error) {
log.error("Can not sign in", error);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Can not auth user", error);
}
}
@Node
@Data
public class UserModel {
@Id
@GeneratedValue
private Long id;
private String username;
private String email;
private String password;
private String avatar;
private String profile;
}
public interface AuthRepository extends Neo4jRepository<UserModel, Long> {
UserModel findByEmail(String mail);
UserModel findByUsername(String username);
}
public interface AuthService {
void signUp(SignUpRequestDto request) throws Exception;
Mono<HashMap<String, Object>> signIn(SignInRequestDto request) throws Exception;
Mono<HashMap<String, Object>> createRefreshToken(UUID uuid) throws Exception;
}
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private AuthRepository authRepository;
@Autowired
private RefreshTokenRepository refreshTokenRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private JwtUtil jwtUtil;
@Override
public void signUp(SignUpRequestDto request) throws Exception {
String username = request.getUsername();
UserModel existUserName = authRepository.findByUsername(username);
if (existUserName != null) {
throw new Exception("Username exist");
}
String email = request.getEmail();
UserModel existUser = authRepository.findByEmail(email);
if (existUser != null) {
throw new Exception("User exist");
}
UserModel userModel = new UserModel();
userModel.setUsername(username);
userModel.setEmail(email);
userModel.setAvatar("");
userModel.setProfile("");
String password = request.getPassword();
String hashPassword = passwordEncoder.encode(password);
userModel.setPassword(hashPassword);
authRepository.save(userModel);
}
@Override
public Mono<HashMap<String, Object>> signIn(SignInRequestDto request) throws Exception {
String email = request.getEmail();
UserModel userModel = authRepository.findByEmail(email);
if (userModel == null) {
throw new Exception("Wrong username or password");
}
if (!passwordEncoder.matches(request.getPassword(), userModel.getPassword())) {
throw new Exception("Wrong username or password");
}
User user = new User();
user.setMail(email);
user.setPassword(userModel.getPassword());
UUID refreshTokenUuid = UUID.randomUUID();
RefreshTokenModel refreshTokenModel = new RefreshTokenModel();
refreshTokenModel.setEmail(email);
refreshTokenModel.setToken(refreshTokenUuid);
refreshTokenModel.setExpiryDate(Instant.now().plusSeconds(3600));
refreshTokenRepository.save(refreshTokenModel);
HashMap<String, Object> response = new HashMap<>();
response.put("accessToken", jwtUtil.generateToken(user));
response.put("refreshToken", refreshTokenUuid);
return Mono.just(response);
}
private Boolean verifyExpiration(RefreshTokenModel refreshToken) {
if (refreshToken.getExpiryDate().compareTo(Instant.now()) < 0) {
return false;
}
return true;
}
@Override
public Mono<HashMap<String, Object>> createRefreshToken(UUID refreshToken) throws Exception {
if (refreshToken == null) {
throw new Exception("Empty refresh token");
}
RefreshTokenModel refreshTokenModel = refreshTokenRepository.findByToken(refreshToken);
if (refreshTokenModel == null) {
refreshTokenRepository.deleteByOldToken(refreshToken.toString());
throw new Exception("Refresh token not found");
}
if (!verifyExpiration(refreshTokenModel)) {
refreshTokenRepository.deleteByToken(refreshToken.toString());
throw new Exception("Refresh token is expired");
}
String email = refreshTokenModel.getEmail();
if (email == null) {
throw new Exception("User not found");
}
User user = new User();
user.setMail(email);
List<UUID> oldRefreshToken = refreshTokenModel.getOldToken();
if (oldRefreshToken == null) {
oldRefreshToken = new ArrayList<>();
}
oldRefreshToken.add(refreshToken);
UUID newRefreshToken = UUID.randomUUID();
refreshTokenModel.setToken(newRefreshToken);
refreshTokenModel.setExpiryDate(Instant.now().plusSeconds(3600));
refreshTokenModel.setOldToken(oldRefreshToken);
refreshTokenRepository.save(refreshTokenModel);
HashMap<String, Object> response = new HashMap<>();
response.put("accessToken", jwtUtil.generateToken(user));
response.put("refreshToken", newRefreshToken);
return Mono.just(response);
}
}
Bạn có thể xem toàn bộ source code ở đây .