Merge pull request #1 from hakimfauzi23/with-http-headers

Change from Http Cookies to Http Headers JWT Mechanism
This commit is contained in:
Hanif Fauzi Hakim
2024-01-04 13:35:17 +07:00
committed by GitHub
7 changed files with 139 additions and 117 deletions

View File

@@ -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.User;
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.request.LoginRequest; 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.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.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.JwtUtils;
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.exception.TokenRefreshException; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.jwt.exception.TokenRefreshException;
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.RoleRepository; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.RoleRepository;
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.UserRepository; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.repository.UserRepository;
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.RefreshTokenService; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.RefreshTokenService;
import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsImpl; import com.hakimfauzi23.boilerplatespringsecurity.modules.auth.service.UserDetailsImpl;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; 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.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -30,7 +29,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -106,61 +104,37 @@ public class AuthController {
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication); SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails);
List<String> roles = userDetails.getAuthorities().stream() List<String> roles = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority) .map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()); .collect(Collectors.toList());
RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId()); RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());
ResponseCookie jwtRefreshCookie = jwtUtils.generateRefreshJwtCookie(refreshToken.getToken()); return ResponseEntity.ok(new JwtResponse(
jwt,
return ResponseEntity.ok() refreshToken.getToken(),
.header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) userDetails.getId(),
.header(HttpHeaders.SET_COOKIE, jwtRefreshCookie.toString()) userDetails.getUsername(),
.body(new UserInfoResponse(userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles)); 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!"));
} }
@PostMapping("/refresh-token") @PostMapping("/refresh-token")
public ResponseEntity<?> refreshToken(HttpServletRequest request) { public ResponseEntity<?> refreshToken(@Valid @RequestBody TokenRefreshRequest request) {
String refreshToken = jwtUtils.getJwtRefreshFromCookies(request); String requestRefreshToken = request.getRefreshToken();
if ((refreshToken != null) && (refreshToken.length() > 0)) { return refreshTokenService.findByToken(requestRefreshToken)
return refreshTokenService.findByToken(refreshToken) .map(refreshTokenService::verifyExpiration)
.map(refreshTokenService::verifyExpiration) .map(RefreshToken::getUser)
.map(RefreshToken::getUser) .map(user -> {
.map(user -> { String token = jwtUtils.generateTokenFromUsername(user.getUsername());
ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(user); return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
})
return ResponseEntity.ok() .orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
.header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) "Refresh token is not in database!"));
.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!"));
} }
} }

View File

@@ -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;
}
}

View File

@@ -2,19 +2,49 @@ package com.hakimfauzi23.boilerplatespringsecurity.modules.auth.data.payload.res
import java.util.List; import java.util.List;
public class UserInfoResponse { public class JwtResponse {
private String token;
private String type = "Bearer";
private String refreshToken;
private Long id; private Long id;
private String username; private String username;
private String email; private String email;
private List<String> roles; private List<String> roles;
public UserInfoResponse(Long id, String username, String email, List<String> roles) { public JwtResponse(String token, String refreshToken, Long id, String username, String email, List<String> roles) {
this.token = token;
this.refreshToken = refreshToken;
this.id = id; this.id = id;
this.username = username; this.username = username;
this.email = email; this.email = email;
this.roles = roles; 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() { public Long getId() {
return id; return id;
} }

View File

@@ -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;
}
}

View File

@@ -10,6 +10,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.io.IOException;
@@ -49,7 +50,12 @@ public class AuthTokenFilter extends OncePerRequestFilter {
} }
private String parseJwt(HttpServletRequest request) { private String parseJwt(HttpServletRequest request) {
String jwt = jwtUtils.getJwtFromCookies(request); String headerAuth = request.getHeader("Authorization");
return jwt;
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
} }
} }

View File

@@ -11,6 +11,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseCookie;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
@@ -28,52 +29,31 @@ public class JwtUtils {
@Value("${spring.app.jwtExpirationMs}") @Value("${spring.app.jwtExpirationMs}")
private int jwtExpirationMs; private int jwtExpirationMs;
@Value("${spring.app.jwtCookieName}") public String generateJwtToken(Authentication authentication) {
private String jwtCookie;
@Value("${spring.app.jwtRefreshCookieName}") UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
private String jwtRefreshCookie; 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() { private Key key() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); 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) { public boolean validateJwtToken(String authToken) {
try { try {
Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken); Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken);
@@ -91,25 +71,4 @@ public class JwtUtils {
return false; 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;
}
}
} }

View File

@@ -4,8 +4,6 @@ spring.datasource.password=
spring.jpa.hibernate.ddl-auto= update spring.jpa.hibernate.ddl-auto= update
# App Properties # App Properties
spring.app.jwtCookieName= cookie-jwt-for-security spring.app.jwtSecret= ======================BezKoder=Spring===========================
spring.app.jwtRefreshCookieName= cookie-refresh-jwt-for-security spring.app.jwtExpirationMs= 60000
spring.app.jwtSecret= 9sL3p2mGzN7oR4Dx8QcY1uKwF5BhVtX6EaJgU3iZqOyMlIbCnAeHrWfPd0 spring.app.jwtRefreshExpirationMs= 259200000
spring.app.jwtExpirationMs= 86400000
spring.app.jwtRefreshExpirationMs= 259200000