Skip to content

Commit f05ab95

Browse files
committed
Add Refresh Token
1 parent a344db0 commit f05ab95

File tree

10 files changed

+264
-6
lines changed

10 files changed

+264
-6
lines changed

src/main/java/com/bezkoder/springjwt/controllers/AuthController.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77

88
import javax.validation.Valid;
99

10+
import com.bezkoder.springjwt.exception.TokenRefreshException;
11+
import com.bezkoder.springjwt.models.RefreshToken;
12+
import com.bezkoder.springjwt.payload.request.LogOutRequest;
13+
import com.bezkoder.springjwt.payload.request.TokenRefreshRequest;
14+
import com.bezkoder.springjwt.payload.response.TokenRefreshResponse;
15+
import com.bezkoder.springjwt.security.services.RefreshTokenService;
1016
import org.springframework.beans.factory.annotation.Autowired;
1117
import org.springframework.http.ResponseEntity;
1218
import org.springframework.security.authentication.AuthenticationManager;
@@ -48,6 +54,9 @@ public class AuthController {
4854
@Autowired
4955
PasswordEncoder encoder;
5056

57+
@Autowired
58+
RefreshTokenService refreshTokenService;
59+
5160
@Autowired
5261
JwtUtils jwtUtils;
5362

@@ -65,11 +74,17 @@ public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest login
6574
.map(item -> item.getAuthority())
6675
.collect(Collectors.toList());
6776

68-
return ResponseEntity.ok(new JwtResponse(jwt,
69-
userDetails.getId(),
70-
userDetails.getUsername(),
71-
userDetails.getEmail(),
72-
roles));
77+
RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());
78+
79+
return ResponseEntity.ok(
80+
new JwtResponse(
81+
jwt,
82+
refreshToken.getToken(),
83+
userDetails.getId(),
84+
userDetails.getUsername(),
85+
userDetails.getEmail(),
86+
roles
87+
));
7388
}
7489

7590
@PostMapping("/signup")
@@ -126,4 +141,25 @@ public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRe
126141

127142
return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
128143
}
144+
145+
@PostMapping("/refreshtoken")
146+
public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) {
147+
String requestRefreshToken = request.getRefreshToken();
148+
149+
return refreshTokenService.findByToken(requestRefreshToken)
150+
.map(refreshTokenService::verifyExpiration)
151+
.map(RefreshToken::getUser)
152+
.map(user -> {
153+
String token = jwtUtils.generateTokenFromUsername(user.getUsername());
154+
return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
155+
})
156+
.orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
157+
"Refresh token is not in database!"));
158+
}
159+
160+
@PostMapping("/logout")
161+
public ResponseEntity<?> logoutUser(@Valid @RequestBody LogOutRequest logOutRequest) {
162+
refreshTokenService.deleteByUserId(logOutRequest.getUserId());
163+
return ResponseEntity.ok(new MessageResponse("Log out successful!"));
164+
}
129165
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.bezkoder.springjwt.exception;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.web.bind.annotation.ResponseStatus;
5+
6+
@ResponseStatus(HttpStatus.FORBIDDEN)
7+
public class TokenRefreshException extends RuntimeException {
8+
9+
private static final long serialVersionUID = 1L;
10+
11+
public TokenRefreshException(String token, String message) {
12+
super(String.format("Failed for [%s]: %s", token, message));
13+
}
14+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.bezkoder.springjwt.models;
2+
3+
import java.time.Instant;
4+
5+
import javax.persistence.*;
6+
7+
@Entity(name = "refreshtoken")
8+
public class RefreshToken {
9+
@Id
10+
@GeneratedValue(strategy = GenerationType.AUTO)
11+
private long id;
12+
13+
@OneToOne
14+
@JoinColumn(name = "user_id", referencedColumnName = "id")
15+
private User user;
16+
17+
@Column(nullable = false, unique = true)
18+
private String token;
19+
20+
@Column(nullable = false)
21+
private Instant expiryDate;
22+
23+
public RefreshToken() {
24+
}
25+
26+
public long getId() {
27+
return id;
28+
}
29+
30+
public void setId(long id) {
31+
this.id = id;
32+
}
33+
34+
public User getUser() {
35+
return user;
36+
}
37+
38+
public void setUser(User user) {
39+
this.user = user;
40+
}
41+
42+
public String getToken() {
43+
return token;
44+
}
45+
46+
public void setToken(String token) {
47+
this.token = token;
48+
}
49+
50+
public Instant getExpiryDate() {
51+
return expiryDate;
52+
}
53+
54+
public void setExpiryDate(Instant expiryDate) {
55+
this.expiryDate = expiryDate;
56+
}
57+
58+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.bezkoder.springjwt.payload.request;
2+
3+
public class LogOutRequest {
4+
private Long userId;
5+
6+
public Long getUserId() {
7+
return this.userId;
8+
}
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.bezkoder.springjwt.payload.request;
2+
3+
import javax.validation.constraints.NotBlank;
4+
5+
public class TokenRefreshRequest {
6+
@NotBlank
7+
private String refreshToken;
8+
9+
public String getRefreshToken() {
10+
return refreshToken;
11+
}
12+
13+
public void setRefreshToken(String refreshToken) {
14+
this.refreshToken = refreshToken;
15+
}
16+
}

src/main/java/com/bezkoder/springjwt/payload/response/JwtResponse.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
public class JwtResponse {
66
private String token;
77
private String type = "Bearer";
8+
private String refreshToken;
89
private Long id;
910
private String username;
1011
private String email;
1112
private List<String> roles;
1213

13-
public JwtResponse(String accessToken, Long id, String username, String email, List<String> roles) {
14+
public JwtResponse(String accessToken, String refreshToken, Long id, String username, String email, List<String> roles) {
1415
this.token = accessToken;
16+
this.refreshToken = refreshToken;
1517
this.id = id;
1618
this.username = username;
1719
this.email = email;
@@ -61,4 +63,12 @@ public void setUsername(String username) {
6163
public List<String> getRoles() {
6264
return roles;
6365
}
66+
67+
public String getRefreshToken() {
68+
return refreshToken;
69+
}
70+
71+
public void setRefreshToken(String refreshToken) {
72+
this.refreshToken = refreshToken;
73+
}
6474
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.bezkoder.springjwt.payload.response;
2+
3+
public class TokenRefreshResponse {
4+
private String accessToken;
5+
private String refreshToken;
6+
private String tokenType = "Bearer";
7+
8+
public TokenRefreshResponse(String accessToken, String refreshToken) {
9+
this.accessToken = accessToken;
10+
this.refreshToken = refreshToken;
11+
}
12+
13+
public String getAccessToken() {
14+
return accessToken;
15+
}
16+
17+
public void setAccessToken(String token) {
18+
this.accessToken = token;
19+
}
20+
21+
public String getRefreshToken() {
22+
return refreshToken;
23+
}
24+
25+
public void setRefreshToken(String refreshToken) {
26+
this.refreshToken = refreshToken;
27+
}
28+
29+
public String getTokenType() {
30+
return tokenType;
31+
}
32+
33+
public void setTokenType(String tokenType) {
34+
this.tokenType = tokenType;
35+
}
36+
37+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.bezkoder.springjwt.repository;
2+
3+
import java.util.Optional;
4+
5+
import com.bezkoder.springjwt.models.RefreshToken;
6+
import com.bezkoder.springjwt.models.User;
7+
import org.springframework.data.jpa.repository.JpaRepository;
8+
import org.springframework.data.jpa.repository.Modifying;
9+
import org.springframework.stereotype.Repository;
10+
11+
@Repository
12+
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
13+
Optional<RefreshToken> findByToken(String token);
14+
15+
@Modifying
16+
int deleteByUser(User user);
17+
}

src/main/java/com/bezkoder/springjwt/security/jwt/JwtUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public String generateJwtToken(Authentication authentication) {
3333
.compact();
3434
}
3535

36+
public String generateTokenFromUsername(String username) {
37+
return Jwts.builder().setSubject(username).setIssuedAt(new Date())
38+
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret)
39+
.compact();
40+
}
41+
3642
public String getUserNameFromJwtToken(String token) {
3743
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
3844
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.bezkoder.springjwt.security.services;
2+
3+
import java.time.Instant;
4+
import java.util.Optional;
5+
import java.util.UUID;
6+
7+
import com.bezkoder.springjwt.exception.TokenRefreshException;
8+
import com.bezkoder.springjwt.models.RefreshToken;
9+
import com.bezkoder.springjwt.repository.RefreshTokenRepository;
10+
import com.bezkoder.springjwt.repository.UserRepository;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.beans.factory.annotation.Value;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
15+
16+
@Service
17+
public class RefreshTokenService {
18+
@Value("${bezkoder.app.jwtRefreshExpirationMs}")
19+
private Long refreshTokenDurationMs;
20+
21+
@Autowired
22+
private RefreshTokenRepository refreshTokenRepository;
23+
24+
@Autowired
25+
private UserRepository userRepository;
26+
27+
public Optional<RefreshToken> findByToken(String token) {
28+
return refreshTokenRepository.findByToken(token);
29+
}
30+
31+
public RefreshToken createRefreshToken(Long userId) {
32+
RefreshToken refreshToken = new RefreshToken();
33+
34+
refreshToken.setUser(userRepository.findById(userId).get());
35+
refreshToken.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
36+
refreshToken.setToken(UUID.randomUUID().toString());
37+
38+
refreshToken = refreshTokenRepository.save(refreshToken);
39+
return refreshToken;
40+
}
41+
42+
public RefreshToken verifyExpiration(RefreshToken token) {
43+
if (token.getExpiryDate().compareTo(Instant.now()) < 0) {
44+
refreshTokenRepository.delete(token);
45+
throw new TokenRefreshException(token.getToken(), "Refresh token was expired. Please make a new signin request");
46+
}
47+
48+
return token;
49+
}
50+
51+
@Transactional
52+
public int deleteByUserId(Long userId) {
53+
return refreshTokenRepository.deleteByUser(userRepository.findById(userId).get());
54+
}
55+
}

0 commit comments

Comments
 (0)