Skip to content

Commit db281d1

Browse files
committed
update refresh token backend
1 parent c5b5496 commit db281d1

13 files changed

Lines changed: 214 additions & 13 deletions

File tree

01-backend/src/main/java/com/josephhieu/feedbackonline/common/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
4646
.authenticationEntryPoint(jwtAuthenticationEntryPoint))
4747

4848
.authorizeHttpRequests(auth -> auth
49-
.requestMatchers("/api/v1/auth/**").permitAll()
49+
.requestMatchers("/api/v1/auth/login", "/api/v1/auth/refresh").permitAll()
5050
.requestMatchers("/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
5151

5252
.requestMatchers("/api/v1/templates/**").authenticated()

01-backend/src/main/java/com/josephhieu/feedbackonline/common/exception/ErrorCode.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ public enum ErrorCode {
2626
CONFIRM_PASSWORD_NOT_MATCH(1013, "Xác nhận mật khẩu mới không khớp", HttpStatus.BAD_REQUEST),
2727
RESET_PASSWORD_INVALID(1014, "Mật khẩu xác nhận xóa hệ thống không chính xác", HttpStatus.BAD_REQUEST),
2828

29+
// REFRESH TOKEN ERRORS
30+
REFRESH_TOKEN_NOT_FOUND(1101, "Refresh Token không tồn tại trong hệ thống", HttpStatus.NOT_FOUND),
31+
REFRESH_TOKEN_EXPIRED(1102, "Refresh Token đã hết hạn, vui lòng đăng nhập lại", HttpStatus.UNAUTHORIZED),
32+
2933
// TEMPLATE ERRORS
3034
TEMPLATE_NOT_EXISTED(2001, "Mẫu feedback không tồn tại", HttpStatus.NOT_FOUND),
3135
TEMPLATE_EXISTED(2002, "Tên mẫu feedback đã tồn tại", HttpStatus.BAD_REQUEST),

01-backend/src/main/java/com/josephhieu/feedbackonline/common/security/JwtTokenProvider.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,48 @@ public class JwtTokenProvider {
2424
@Value("${application.security.jwt.expiration}")
2525
private long jwtExpiration;
2626

27+
@Value("${application.security.jwt.refresh-token-expiration}")
28+
private long refreshTokenExpiration;
29+
2730
private SecretKey getSigningKey() {
2831
return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
2932
}
3033

3134
public String generateToken(Authentication authentication) {
3235
CustomUserDetails userPrincipal = (CustomUserDetails) authentication.getPrincipal();
33-
34-
Date now = new Date();
35-
Date expiryDate = new Date(now.getTime() + jwtExpiration);
36-
37-
// Lấy quyền (ROLE_ADMIN)
3836
String authorities = authentication.getAuthorities().stream()
3937
.map(GrantedAuthority::getAuthority)
4038
.collect(Collectors.joining(" "));
4139

40+
return buildToken(userPrincipal.getUsername(), authorities, jwtExpiration);
41+
}
42+
43+
public String generateTokenFromUsername(String username, String role) {
44+
return buildToken(username, role, jwtExpiration);
45+
}
46+
47+
private String buildToken(String subject, String scope, long expirationTime) {
48+
Date now = new Date();
49+
Date expiryDate = new Date(now.getTime() + expirationTime);
50+
4251
return Jwts.builder()
43-
.subject(userPrincipal.getUsername()) // 0.12.x dùng .subject()
52+
.subject(subject)
4453
.issuedAt(now)
4554
.expiration(expiryDate)
46-
.claim("scope", authorities)
55+
.claim("scope", scope) // Lưu role vào claim "scope"
4756
.signWith(getSigningKey())
4857
.compact();
4958
}
5059

60+
public String getRoleFromJWT(String token) {
61+
return Jwts.parser()
62+
.verifyWith(getSigningKey())
63+
.build()
64+
.parseSignedClaims(token)
65+
.getPayload()
66+
.get("scope", String.class);
67+
}
68+
5169
public String getUsernameFromJWT(String token) {
5270
return Jwts.parser()
5371
.verifyWith(getSigningKey()) // 0.12.x dùng .verifyWith()

01-backend/src/main/java/com/josephhieu/feedbackonline/controller/AuthController.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public class AuthController {
2525
public ResponseEntity<ApiResponse<AuthResponse>> login(@RequestBody @Valid LoginRequest request) {
2626

2727
AuthResponse result = authService.login(request);
28-
2928
ApiResponse<AuthResponse> response = ApiResponse.<AuthResponse>builder()
3029
.result(result)
3130
.message("Đăng nhập thành công")
@@ -34,6 +33,17 @@ public ResponseEntity<ApiResponse<AuthResponse>> login(@RequestBody @Valid Login
3433
return ResponseEntity.ok(response);
3534
}
3635

36+
@PostMapping("/refresh")
37+
public ResponseEntity<ApiResponse<AuthResponse>> refresh(@RequestBody String refreshToken) {
38+
39+
AuthResponse result = authService.refreshToken(refreshToken);
40+
41+
return ResponseEntity.ok(ApiResponse.<AuthResponse>builder()
42+
.result(result)
43+
.message("Làm mới phiên làm việc thành công")
44+
.build());
45+
}
46+
3747
@PostMapping("/change-password")
3848
public ResponseEntity<ApiResponse<Void>> changePassword(
3949
@RequestBody @Valid PasswordChangeRequest request,

01-backend/src/main/java/com/josephhieu/feedbackonline/dto/response/AuthResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
public class AuthResponse {
1212

1313
String token;
14+
String refreshToken;
1415
String username;
1516
String role;
1617
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.josephhieu.feedbackonline.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
6+
import java.time.Instant;
7+
import java.util.UUID;
8+
9+
@Entity
10+
@Table(name = "refresh_tokens")
11+
@Getter @Setter
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
@Builder
15+
public class RefreshToken {
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.UUID)
19+
@Column(name = "id")
20+
private UUID id;
21+
22+
@Column(name = "token", nullable = false, unique = true)
23+
private String token;
24+
25+
@Column(name = "expiry_date", nullable = false)
26+
private Instant expiryDate;
27+
28+
@Column(name = "username", nullable = false)
29+
private String username;
30+
31+
@Column(name = "role", nullable = false)
32+
private String role;
33+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.josephhieu.feedbackonline.repository;
2+
3+
import com.josephhieu.feedbackonline.entity.RefreshToken;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Modifying;
6+
import org.springframework.stereotype.Repository;
7+
8+
import java.util.Optional;
9+
import java.util.UUID;
10+
11+
@Repository
12+
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, UUID> {
13+
Optional<RefreshToken> findByToken(String token);
14+
15+
@Modifying
16+
void deleteByUsername(String username);
17+
}

01-backend/src/main/java/com/josephhieu/feedbackonline/service/AuthService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ public interface AuthService {
88

99
AuthResponse login(LoginRequest request);
1010

11+
AuthResponse refreshToken(String refreshToken);
12+
1113
void changePassword(PasswordChangeRequest request);
1214
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.josephhieu.feedbackonline.service;
2+
3+
import com.josephhieu.feedbackonline.entity.RefreshToken;
4+
5+
import java.util.Optional;
6+
7+
public interface RefreshTokenService {
8+
9+
RefreshToken createRefreshToken(String username, String role);
10+
11+
Optional<RefreshToken> findByToken(String token);
12+
13+
RefreshToken verifyExpiration(RefreshToken token);
14+
15+
void deleteByUsername(String username);
16+
}

01-backend/src/main/java/com/josephhieu/feedbackonline/service/impl/AuthServiceImpl.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.josephhieu.feedbackonline.repository.AdminRepository;
1313
import com.josephhieu.feedbackonline.repository.HocVienRepository;
1414
import com.josephhieu.feedbackonline.service.AuthService;
15+
import com.josephhieu.feedbackonline.service.RefreshTokenService;
1516
import lombok.RequiredArgsConstructor;
1617
import lombok.extern.slf4j.Slf4j;
1718
import org.springframework.security.authentication.AuthenticationManager;
@@ -35,28 +36,35 @@ public class AuthServiceImpl implements AuthService {
3536
private final HocVienRepository hocVienRepository;
3637
private final PasswordEncoder passwordEncoder;
3738

39+
private final RefreshTokenService refreshTokenService;
40+
3841
@Override
42+
@Transactional
3943
public AuthResponse login(LoginRequest request) {
40-
4144
try {
4245
Authentication authentication = authenticationManager.authenticate(
4346
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
4447
);
4548

4649
SecurityContextHolder.getContext().setAuthentication(authentication);
4750

48-
String token = tokenProvider.generateToken(authentication);
51+
// 2. Tạo Access Token
52+
String accessToken = tokenProvider.generateToken(authentication);
4953

5054
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
5155
String role = userDetails.getAuthorities().stream()
5256
.map(GrantedAuthority::getAuthority)
5357
.findFirst()
5458
.orElse("ROLE_USER");
5559

60+
// 3. Tạo và lưu Refresh Token vào Database
61+
var refreshToken = refreshTokenService.createRefreshToken(userDetails.getUsername(), role);
62+
5663
log.info("Login successful for user: {}", userDetails.getUsername());
5764

5865
return AuthResponse.builder()
59-
.token(token)
66+
.token(accessToken)
67+
.refreshToken(refreshToken.getToken())
6068
.username(userDetails.getUsername())
6169
.role(role)
6270
.build();
@@ -66,6 +74,31 @@ public AuthResponse login(LoginRequest request) {
6674
}
6775
}
6876

77+
// 4. Implement phương thức refreshToken
78+
@Override
79+
@Transactional
80+
public AuthResponse refreshToken(String requestToken) {
81+
return refreshTokenService.findByToken(requestToken)
82+
.map(refreshTokenService::verifyExpiration)
83+
.map(refreshToken -> {
84+
// Cấp Access Token mới dựa trên thông tin trong Refresh Token
85+
String newAccessToken = tokenProvider.generateTokenFromUsername(
86+
refreshToken.getUsername(),
87+
refreshToken.getRole()
88+
);
89+
90+
log.info("Access Token refreshed for user: {}", refreshToken.getUsername());
91+
92+
return AuthResponse.builder()
93+
.token(newAccessToken)
94+
.refreshToken(refreshToken.getToken())
95+
.username(refreshToken.getUsername())
96+
.role(refreshToken.getRole())
97+
.build();
98+
})
99+
.orElseThrow(() -> new AppException(ErrorCode.REFRESH_TOKEN_NOT_FOUND));
100+
}
101+
69102
@Transactional
70103
public void changePassword(PasswordChangeRequest request) {
71104
// 1. Lấy thông tin từ SecurityContext

0 commit comments

Comments
 (0)