Merge pull request #1 from hakimfauzi23/with-http-headers
Change from Http Cookies to Http Headers JWT Mechanism
This commit is contained in:
@@ -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<String> 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!"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<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.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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
spring.app.jwtSecret= ======================BezKoder=Spring===========================
|
||||
spring.app.jwtExpirationMs= 60000
|
||||
spring.app.jwtRefreshExpirationMs= 259200000
|
||||
Reference in New Issue
Block a user