From 94ce6a64241a72fefdcf1a4a06ecc76f7fdfabc5 Mon Sep 17 00:00:00 2001 From: Hanif Date: Thu, 4 Jan 2024 13:33:40 +0700 Subject: [PATCH] Change from Http Cookies to Http Headers JWT Mechanism --- .../auth/controllers/AuthController.java | 72 ++++++----------- .../payload/request/TokenRefreshRequest.java | 18 +++++ ...UserInfoResponse.java => JwtResponse.java} | 34 +++++++- .../response/TokenRefreshResponse.java | 37 +++++++++ .../modules/auth/jwt/AuthTokenFilter.java | 10 ++- .../modules/auth/jwt/JwtUtils.java | 77 +++++-------------- src/main/resources/application.properties | 8 +- 7 files changed, 139 insertions(+), 117 deletions(-) create mode 100644 src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/request/TokenRefreshRequest.java rename src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/{UserInfoResponse.java => JwtResponse.java} (50%) create mode 100644 src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/TokenRefreshResponse.java diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java index 4f11f7c..db247de 100644 --- a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/controllers/AuthController.java @@ -6,19 +6,18 @@ import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.Role; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.User; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request.LoginRequest; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request.SignupRequest; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request.TokenRefreshRequest; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.response.JwtResponse; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.response.MessageResponse; -import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.response.UserInfoResponse; +import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.response.TokenRefreshResponse; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.JwtUtils; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.exception.TokenRefreshException; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.RoleRepository; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.UserRepository; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.RefreshTokenService; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsImpl; -import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -30,7 +29,6 @@ import org.springframework.web.bind.annotation.*; import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -106,61 +104,37 @@ public class AuthController { .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = jwtUtils.generateJwtToken(authentication); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); - - ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); - List roles = userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId()); - ResponseCookie jwtRefreshCookie = jwtUtils.generateRefreshJwtCookie(refreshToken.getToken()); - - return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) - .header(HttpHeaders.SET_COOKIE, jwtRefreshCookie.toString()) - .body(new UserInfoResponse(userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); - } - - @PostMapping("/signout") - public ResponseEntity logoutUser() { - Object principle = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - if (!Objects.equals(principle.toString(), "anonymousUser")) { - Long userId = ((UserDetailsImpl) principle).getId(); - refreshTokenService.deleteByUserId(userId); - } - - ResponseCookie jwtCookie = jwtUtils.getCleanJwtCookie(); - ResponseCookie jwtRefreshCookie = jwtUtils.getCleanJwtRefreshCookie(); - - return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) - .header(HttpHeaders.SET_COOKIE, jwtRefreshCookie.toString()) - .body(new MessageResponse("You've been signed out!")); + return ResponseEntity.ok(new JwtResponse( + jwt, + refreshToken.getToken(), + userDetails.getId(), + userDetails.getUsername(), + userDetails.getEmail(), + roles)); } @PostMapping("/refresh-token") - public ResponseEntity refreshToken(HttpServletRequest request) { - String refreshToken = jwtUtils.getJwtRefreshFromCookies(request); + public ResponseEntity refreshToken(@Valid @RequestBody TokenRefreshRequest request) { + String requestRefreshToken = request.getRefreshToken(); - if ((refreshToken != null) && (refreshToken.length() > 0)) { - return refreshTokenService.findByToken(refreshToken) - .map(refreshTokenService::verifyExpiration) - .map(RefreshToken::getUser) - .map(user -> { - ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(user); - - return ResponseEntity.ok() - .header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) - .body(new MessageResponse("Token is refreshed successfully!")); - }) - .orElseThrow(() -> new TokenRefreshException(refreshToken, - "Refresh token is not in database!")); - } - - return ResponseEntity.badRequest().body(new MessageResponse("Refresh Token is empty!")); + 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!")); } + } diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/request/TokenRefreshRequest.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/request/TokenRefreshRequest.java new file mode 100644 index 0000000..f40d824 --- /dev/null +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/request/TokenRefreshRequest.java @@ -0,0 +1,18 @@ +package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request; + +import jakarta.validation.constraints.NotBlank; + +public class TokenRefreshRequest { + + @NotBlank + private String refreshToken; + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + +} diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/UserInfoResponse.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/JwtResponse.java similarity index 50% rename from src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/UserInfoResponse.java rename to src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/JwtResponse.java index 3d7c0bb..d3a36d6 100644 --- a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/UserInfoResponse.java +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/JwtResponse.java @@ -2,19 +2,49 @@ package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.res import java.util.List; -public class UserInfoResponse { +public class JwtResponse { + + private String token; + private String type = "Bearer"; + private String refreshToken; private Long id; private String username; private String email; private List roles; - public UserInfoResponse(Long id, String username, String email, List roles) { + public JwtResponse(String token, String refreshToken, Long id, String username, String email, List roles) { + this.token = token; + this.refreshToken = refreshToken; this.id = id; this.username = username; this.email = email; this.roles = roles; } + public String getAccessToken() { + return token; + } + + public void setAccessToken(String accessToken) { + this.token = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getTokenType() { + return type; + } + + public void setTokenType(String tokenType) { + this.type = tokenType; + } + public Long getId() { return id; } diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/TokenRefreshResponse.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/TokenRefreshResponse.java new file mode 100644 index 0000000..d07da43 --- /dev/null +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/data/payload/response/TokenRefreshResponse.java @@ -0,0 +1,37 @@ +package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.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; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } +} diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/AuthTokenFilter.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/AuthTokenFilter.java index 3c0b3ae..f75246e 100644 --- a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/AuthTokenFilter.java +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/AuthTokenFilter.java @@ -10,6 +10,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -49,7 +50,12 @@ public class AuthTokenFilter extends OncePerRequestFilter { } private String parseJwt(HttpServletRequest request) { - String jwt = jwtUtils.getJwtFromCookies(request); - return jwt; + String headerAuth = request.getHeader("Authorization"); + + if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) { + return headerAuth.substring(7); + } + + return null; } } diff --git a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/JwtUtils.java b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/JwtUtils.java index 029933e..ace13d6 100644 --- a/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/JwtUtils.java +++ b/src/main/java/com/hakimfauzi23/boilerplatespringsecurity/modules/auth/jwt/JwtUtils.java @@ -11,6 +11,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseCookie; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import org.springframework.web.util.WebUtils; @@ -28,52 +29,31 @@ public class JwtUtils { @Value("${spring.app.jwtExpirationMs}") private int jwtExpirationMs; - @Value("${spring.app.jwtCookieName}") - private String jwtCookie; + public String generateJwtToken(Authentication authentication) { - @Value("${spring.app.jwtRefreshCookieName}") - private String jwtRefreshCookie; + UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal(); + return generateTokenFromUsername(userPrincipal.getUsername()); - public String getJwtFromCookies(HttpServletRequest request) { - return getCookieValueByName(request, jwtCookie); - } - - public String getJwtRefreshFromCookies(HttpServletRequest request) { - return getCookieValueByName(request, jwtRefreshCookie); - } - - public ResponseCookie getCleanJwtCookie() { - return ResponseCookie.from(jwtCookie, null).path("/api").build(); - } - - public ResponseCookie getCleanJwtRefreshCookie() { - return ResponseCookie.from(jwtRefreshCookie, null).path("/api/auth/refresh-token").build(); - } - - public ResponseCookie generateJwtCookie(UserDetailsImpl userDetails) { - String jwt = generateTokenFromUsername(userDetails.getUsername()); - return generateCookie(jwtCookie, jwt, "/api"); - } - - public ResponseCookie generateJwtCookie(User user) { - String jwt = generateTokenFromUsername(user.getUsername()); - return generateCookie(jwtCookie, jwt, "/api"); - } - - public ResponseCookie generateRefreshJwtCookie(String refreshToken) { - return generateCookie(jwtRefreshCookie, refreshToken, "/api/auth/refresh-token"); - } - - - public String getUserNameFromJwtToken(String token) { - return Jwts.parserBuilder().setSigningKey(key()).build() - .parseClaimsJws(token).getBody().getSubject(); } private Key key() { return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); } + public String generateTokenFromUsername(String username) { + return Jwts.builder() + .setSubject(username) + .setIssuedAt(new Date()) + .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) + .signWith(SignatureAlgorithm.HS256, jwtSecret) + .compact(); + } + + public String getUserNameFromJwtToken(String token) { + return Jwts.parserBuilder().setSigningKey(key()).build() + .parseClaimsJws(token).getBody().getSubject(); + } + public boolean validateJwtToken(String authToken) { try { Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken); @@ -91,25 +71,4 @@ public class JwtUtils { return false; } - public String generateTokenFromUsername(String username) { - return Jwts.builder() - .setSubject(username) - .setIssuedAt(new Date()) - .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)) - .signWith(key(), SignatureAlgorithm.HS256) - .compact(); - } - - private ResponseCookie generateCookie(String name, String value, String path) { - return ResponseCookie.from(name, value).path(path).maxAge(24 * 60 * 60).httpOnly(true).build(); - } - - private String getCookieValueByName(HttpServletRequest request, String name) { - Cookie cookie = WebUtils.getCookie(request, name); - if (cookie != null) { - return cookie.getValue(); - } else { - return null; - } - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 47111f0..b23b658 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -4,8 +4,6 @@ spring.datasource.password= spring.jpa.hibernate.ddl-auto= update # App Properties -spring.app.jwtCookieName= cookie-jwt-for-security -spring.app.jwtRefreshCookieName= cookie-refresh-jwt-for-security -spring.app.jwtSecret= 9sL3p2mGzN7oR4Dx8QcY1uKwF5BhVtX6EaJgU3iZqOyMlIbCnAeHrWfPd0 -spring.app.jwtExpirationMs= 86400000 -spring.app.jwtRefreshExpirationMs= 259200000 \ No newline at end of file +spring.app.jwtSecret= ======================BezKoder=Spring=========================== +spring.app.jwtExpirationMs= 60000 +spring.app.jwtRefreshExpirationMs= 259200000 \ No newline at end of file