Skip to content

Commit

Permalink
Implementation of Refresh Token Process (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rapter1990 authored Apr 7, 2023
1 parent 69d98b8 commit 55cc08c
Show file tree
Hide file tree
Showing 20 changed files with 265 additions and 392 deletions.
36 changes: 31 additions & 5 deletions src/main/java/com/ays/backend/user/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import com.ays.backend.user.controller.payload.request.AdminRegisterRequest;
import com.ays.backend.user.controller.payload.response.AuthResponse;
import com.ays.backend.user.controller.payload.response.MessageResponse;
import com.ays.backend.user.model.Token;
import com.ays.backend.user.service.AuthService;
import com.ays.backend.util.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down Expand Up @@ -50,17 +53,40 @@ public ResponseEntity<MessageResponse> register(@RequestBody AdminRegisterReques
* @return A ResponseEntity containing an AuthResponse object and the HTTP status code (200 OK).
*/
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody AdminLoginRequest loginRequest) {
public ResponseEntity<AuthResponse> login(@RequestBody AdminLoginRequest loginRequest) { // TODO : username and password send with x-www-url-encoded

final var aysToken = authService.login(loginRequest);
final Token token = authService.login(loginRequest);

AuthResponse authResponse = AuthResponse.builder()
.accessTokenExpireIn(aysToken.getAccessTokenExpireIn())
.refreshToken(aysToken.getRefreshToken())
.accessToken(aysToken.getAccessToken())
.accessTokenExpireIn(token.getAccessTokenExpireIn())
.refreshToken(token.getRefreshToken())
.accessToken(token.getAccessToken())
.build();

return new ResponseEntity<>(authResponse, HttpStatus.OK);
}

/**
* This endpoint allows admin to refresh token.
*
* @param httpServletRequest A HttpServletRequest object used to send a token through its header .
* @return A ResponseEntity containing an AuthResponse object and the HTTP status code (200 OK).
*/
@PostMapping("/refresh-token")
public ResponseEntity<AuthResponse> refreshToken(HttpServletRequest httpServletRequest) {

final String refreshToken = HttpServletRequestWrapper.getToken(httpServletRequest);

final Token renewToken = authService.refreshToken(refreshToken);

AuthResponse authResponse = AuthResponse.builder()
.accessTokenExpireIn(renewToken.getAccessTokenExpireIn())
.refreshToken(renewToken.getRefreshToken())
.accessToken(renewToken.getAccessToken())
.build();

return new ResponseEntity<>(authResponse, HttpStatus.OK);
}


}
14 changes: 6 additions & 8 deletions src/main/java/com/ays/backend/user/controller/ErrorTypes.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package com.ays.backend.user.controller;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
* Keeps error types to be handled in the error GlobalExceptionHandler.
*/
@Getter
@RequiredArgsConstructor
enum ErrorTypes {

UNIQUE_MOBILE_NUMBER("UNIQUEMOBILENUMBER");

private final String reason;

ErrorTypes(String reason) {
this.reason = reason;
}

public String getReason() {
return reason;
}
}
20 changes: 20 additions & 0 deletions src/main/java/com/ays/backend/user/model/Token.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.ays.backend.user.model;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Builder;
import lombok.Getter;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Date;
import java.util.Set;

@Getter
@Builder
Expand All @@ -11,4 +18,17 @@ public class Token {
private Long accessTokenExpireIn;
private String refreshToken;

public static JwtBuilder initializeJwtBuilder(UserDetails userDetails,
Set<String> roles,
long currentTimeMillis,
String appSecret) {

return Jwts.builder()
.claim("roles", roles)
.claim("username", userDetails.getUsername())
.setIssuedAt(new Date(currentTimeMillis))
.setSubject(userDetails.getUsername())
.signWith(SignatureAlgorithm.HS512, appSecret); // TODO : SignatureAlgorithm and APP_SECRET should be read from the database
}

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package com.ays.backend.user.model.entities;

import java.time.LocalDateTime;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.UpdateTimestamp;
import lombok.experimental.SuperBuilder;

import java.time.LocalDateTime;

/**
* Base entity to be used in order to pass the common fields to the entities in the same module.
*/
@MappedSuperclass
@Getter
@Setter
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class BaseEntity {

@Id
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
import com.ays.backend.user.model.enums.UserStatus;
import jakarta.persistence.*;
import jakarta.validation.constraints.Email;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.time.LocalDateTime;
Expand All @@ -18,11 +22,11 @@
uniqueConstraints = {
@UniqueConstraint(name = "UniqueMobileNumber", columnNames = {"countryCode", "lineNumber"})
})
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity extends BaseEntity {

@Column(unique = true, nullable = false)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ays.backend.user.security;

import com.ays.backend.util.HttpServletRequestWrapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -25,8 +26,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

try {
String jwtToken = extractJwtFromRequest(request);
String jwtToken = HttpServletRequestWrapper.getToken(request);
if (StringUtils.hasText(jwtToken) && jwtTokenProvider.validateToken(jwtToken)) {
String username = jwtTokenProvider.getUserNameFromJwtToken(jwtToken);
UserDetails user = userDetailsService.loadUserByUsername(username);
Expand All @@ -42,11 +44,4 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
filterChain.doFilter(request, response);
}

private String extractJwtFromRequest(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer "))
return bearer.substring(7, bearer.length());
return null;
}

}
63 changes: 48 additions & 15 deletions src/main/java/com/ays/backend/user/security/JwtTokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.ays.backend.user.model.Token;
import io.jsonwebtoken.*;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -20,8 +21,11 @@ public class JwtTokenProvider {
@Value("${ays.token.secret}")
private String APP_SECRET;

@Value("${ays.token.expires-in}")
private Long TOKEN_EXPIRE_IN;
@Value("${ays.token.access-expire-minute}")
private Integer ACCESS_TOKEN_EXPIRE_MINUTE;

@Value("${ays.token.refresh-expire-day}")
private Integer REFRESH_TOKEN_EXPIRE_DAY;

public Token generateJwtToken(Authentication auth) {
UserDetails userDetails = (UserDetails) auth.getPrincipal();
Expand All @@ -31,26 +35,55 @@ public Token generateJwtToken(Authentication auth) {
.collect(Collectors.toSet());

final long currentTimeMillis = System.currentTimeMillis();
final Date accessTokenExpireIn = new Date(currentTimeMillis + TOKEN_EXPIRE_IN);
final String accessToken = Jwts.builder()
.setId(UUID.randomUUID().toString())
.setSubject(userDetails.getUsername())
.claim("roles", roles)
.claim("username", userDetails.getUsername())
.setIssuedAt(new Date(currentTimeMillis))
.setExpiration(accessTokenExpireIn)
.signWith(SignatureAlgorithm.HS512, APP_SECRET) // TODO : SignatureAlgorithm and APP_SECRET should be read from the database
.compact();
final JwtBuilder jwtBuilder = Token.initializeJwtBuilder(userDetails, roles, currentTimeMillis, APP_SECRET);
final Date accessTokenExpireIn = DateUtils.addMinutes(new Date(currentTimeMillis), ACCESS_TOKEN_EXPIRE_MINUTE);
final String accessToken = this.generateAccessToken(accessTokenExpireIn, jwtBuilder);

final String refreshToken = this.generateRefreshToken(currentTimeMillis, jwtBuilder);

return Token.builder()
.accessToken(accessToken)
.accessTokenExpireIn(accessTokenExpireIn.getTime())
.refreshToken(refreshToken)
.build();
}

public Token generateJwtToken(Authentication auth, String refreshToken) {

UserDetails userDetails = (UserDetails) auth.getPrincipal();

final Set<String> roles = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());

//RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());
final long currentTimeMillis = System.currentTimeMillis();
final JwtBuilder jwtBuilder = Token.initializeJwtBuilder(userDetails, roles, currentTimeMillis, APP_SECRET);
final Date accessTokenExpireIn = DateUtils.addMinutes(new Date(currentTimeMillis), ACCESS_TOKEN_EXPIRE_MINUTE);
final String accessToken = this.generateAccessToken(accessTokenExpireIn, jwtBuilder);

return Token.builder()
.accessToken(accessToken)
.accessTokenExpireIn(currentTimeMillis + TOKEN_EXPIRE_IN)
// .refreshToken(refreshToken)
.accessTokenExpireIn(accessTokenExpireIn.getTime())
.refreshToken(refreshToken)
.build();
}


private String generateAccessToken(Date accessTokenExpireIn, JwtBuilder jwtBuilder) {
return jwtBuilder
.setId(UUID.randomUUID().toString())
.setExpiration(accessTokenExpireIn)
.compact();
}

private String generateRefreshToken(long currentTimeMillis, JwtBuilder jwtBuilder) {
final Date refreshTokenExpireIn = DateUtils.addDays(new Date(currentTimeMillis), REFRESH_TOKEN_EXPIRE_DAY);
return jwtBuilder
.setId(UUID.randomUUID().toString())
.setExpiration(refreshTokenExpireIn)
.compact();
}

public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody().getSubject();
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/ays/backend/user/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ public interface AuthService {
* Login to platform.
*
* @param loginRequest the loginRequest entity
* @return AuthResponse
* @return Token
*/
Token login(AdminLoginRequest loginRequest);

/**
* Refresh a Token
*
* @param refreshToken the refreshToken text
* @return Token
*/
Token refreshToken(String refreshToken);
}

This file was deleted.

Loading

0 comments on commit 55cc08c

Please sign in to comment.