- vừa được xem lúc

[Spring security] - Spring Boot Security Refresh Token

0 0 30

Người đăng: TheLight

Theo Viblo Asia

Trong bài viết trước, chúng ta đã cùng tìm hiểu về cách xây dựng ứng dụng Spring security với JWT để xác thực và phân quyền. Và như mọi người đã biết thì Access Token sẽ hết hạn sau một khoảng thời gian, vậy làm sao để tạo lại token mới thì trong bài viết này chúng ta sẽ tiếp tục tìm hiểu về JWT Refresh Token.

Mọi người có thể tìm đọc các bài viết liên quan tại đây!

Spring Boot Refresh Token Flow

Dựa trên source code phát triển từ bài viết trước Spring Boot Security - JWT Authentication chúng ta thêm thông tin refreshToken trả về khi login thành công.

Thông thường chúng ta thường cấu hình thời gian hết hạn của Refresh Token sẽ dài hơn của Access Token.

Khi truy cập Server với Access Token đã hết hạn, Client sẽ được yêu cầu lấy lại Access Token mới dựa trên thông tin Refresh Token được cấp trước đó.

Refresh Token Request and Response

Requests

  • TokenRefreshRequest: { refreshToken }

Responses

  • JwtResponse: { accessToken, type, refreshToken, id, username, email, roles }
  • MessageResponse: { message }
  • TokenRefreshResponse: { accessToken, type, refreshToken }

TokenRefreshRequest

package tiendv.example.payload.request;
// imports public class TokenRefreshRequest { @NotBlank private String refreshToken; public String getRefreshToken() { return refreshToken; } public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
}

JwtResponse

// imports import java.util.List; public class JwtResponse { private String token; private String type = "Bearer"; private String refreshToken; private Long id; private String username; private String email; private List<String> roles; public JwtResponse(String accessToken, String refreshToken, Long id, String username, String email, List<String> roles) { this.token = accessToken; this.refreshToken = refreshToken; this.id = id; this.username = username; this.email = email; this.roles = roles; } public String getToken() { return token; } // getter/setter
} 

TokenRefreshResponse

package tiendv.example.payload.response; 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; } // getter/setter
}

Renew

Trong AuthController:

  • update trong api /signin thêm thông tin Refresh Token
  • thêm api refreshToken tạo mới Access Token từ Refresh Token

AuthController

package tiendv.example.controller;
// imports @CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/auth")
public class AuthController { ... @PostMapping("/signin") public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); String jwt = jwtUtils.generateJwtToken(userDetails); List<String> roles = userDetails.getAuthorities().stream() .map(item -> item.getAuthority()) .collect(Collectors.toList()); RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId()); return ResponseEntity.ok(new JwtResponse( jwt, refreshToken.getToken(), userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); } @PostMapping("/refreshtoken") public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) { String requestRefreshToken = request.getRefreshToken(); return refreshTokenService.findByToken(requestRefreshToken) .map(refreshTokenService::verifyExpiration) .map(RefreshToken::getUser) .map(user -> { String token = jwtUtils.generateTokenFromUsername(user.getUsername()); return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken)); }) .orElseThrow(() -> new TokenRefreshException(requestRefreshToken, "Refresh token is not in database!")); }
}

Trong phương thức refreshtoken:

  • Đầu tiên, lấy Refresh Token từ HTTP Request
  • Tiếp theo lấy RefreshToken(id, User, token, expiryDate) trong DB để thực hiện validate, verify.
  • Tiếp tục lấy thông tin User từ RefreshToken để tạo mới Access Token và trả về TokenRefreshResponse
  • Nếu có lỗi xảy ra throw TokenRefreshException

RefreshToken Service

RefreshToken

Class này quan hệ 1-1 với User.

package tiendv.example.model;
// imports @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; public RefreshToken() { } // getter/setter
}

RefreshTokenRepository

package tiendv.example.repository;
// imports @Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> { Optional<RefreshToken> findByToken(String token); @Modifying int deleteByUser(User user);
}

RefreshTokenService

package tiendv.example.security.service;
// imports @Service
public class RefreshTokenService { @Value("${jwt.app.jwtRefreshExpirationMs}") private Long refreshTokenDurationMs; @Autowired private RefreshTokenRepository refreshTokenRepository; @Autowired private UserRepository userRepository; public Optional<RefreshToken> findByToken(String token) { return refreshTokenRepository.findByToken(token); } public RefreshToken createRefreshToken(Long userId) { RefreshToken refreshToken = new RefreshToken(); refreshToken.setUser(userRepository.findById(userId).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()); }
}

Handle Exception

Tạo class TokenRefreshException kế thừa class RuntimeException.

TokenRefreshException

package tiendv.example.exception;
// imports @ResponseStatus(HttpStatus.FORBIDDEN)
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)); }
}

Cuối cùng là tạo class TokenControllerAdvice gắn annotation RestControllerAdvice để hande exception trong ứng dụng: TokenRefreshException

package tiendv.example.advice;
// imports @RestControllerAdvice
public class TokenControllerAdvice { @ExceptionHandler(value = TokenRefreshException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public ErrorMessage handleTokenRefreshException(TokenRefreshException ex, WebRequest request) { return new ErrorMessage( HttpStatus.FORBIDDEN.value(), new Date(), ex.getMessage(), request.getDescription(false)); }
}

Run & Test

Signin (trả về thêm thông tin Refresh Token)

Khi Access Token hết hạn

Lấy lại Access Token từ Refresh Token

Khi Refresh Token hết hạn, yêu cầu login (signin) lại.

Tổng kết

Trên đây là hướng dẫn về cấu hình Spring security (Refresh Token) để xác thực và phân quyền người dùng. Hy vọng mọi người sẽ hiểu được ý tưởng tổng thể của bài viết và áp dụng nó vào dự án của mọi các bạn một cách thoải mái.

Nguồn: https://thenewstack.wordpress.com/2021/11/24/spring-security-spring-boot-security-refresh-token/

Follow me: thenewstack.wordpress.com

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tạo ra virus bằng tool (Part1)

Virus. Tác hại của nó để lại cũng nặng nề:. . Gây khó chịu cho chúng ta là tác hại đầu tiên.

0 0 48

- vừa được xem lúc

Facebook và google "hiểu" chúng ta như thế nào?

Tổng quan. Đã bao giờ bạn gặp những tình huống dưới đây và đặt câu hỏi thắc mắc tại sao chưa.

0 0 48

- vừa được xem lúc

Mã hoá dữ liệu trên Android với Jetpack Security

Jetpack Security (JetSec) là thư viện được xây dựng từ Tink - dự án mã nguồn mở, bảo mật đa nền tảng của Google. Jetpack Security được sử dụng cho việc mã hoá File và SharedPreferences.

0 0 66

- vừa được xem lúc

Tái hiện vụ bị đánh cắp 2 triệu DAI (~2 triệu USD) của Akropolis

Tổng quan. .

0 0 108

- vừa được xem lúc

Bảo mật internet: HTTPS và SSL/TLS như giải thích cho trẻ 5 tuổi

(Mình chém gió đấy, trẻ 5 tuổi còn đang tập đọc mà hiểu được cái này thì là thần đồng, là thiên tài, là mình cũng lạy). . . Xin chào các bạn.

0 0 90

- vừa được xem lúc

Phân biệt server xịn và server pha ke bằng SSL Pinning

Xin chào các bạn, trong bài viết này mình muốn chia sẻ về một kĩ thuật rất nên dùng khi cần tăng tính bảo mật của kết nối internet: SSL Pinning. Trong bài viết trước, mình đã giải thích khá kĩ về SSL,

0 0 584