Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Refresh Token Process #39

Merged
merged 15 commits into from
Apr 7, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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