14 standardize and clean api and fix bruno configuration (#25)
* ADD JWT authentication support with token generation and validation * ADD JWT handling after successful login * ADD user authentication and standardize user retrieval * COMBINE token dtos * ADD JWT authentication filter * IMPROVE token handling * STANDARDIZE API endpoints and improve JWT handling * REMOVE extra logging * REMOVE redundant job existence checks * UPDATE Bruno Google token * REFACTOR some classes * ADD JWT cookie check * ADD AuthProvider and CORS configuration; UPDATE API endpoints for consistency * ADD JWT validation check; * ADD profile picture to database * ADD reload after login to update page * PATCH login issue * REMOVE unused classes * ADJUST logging in JwtFilter * REMOVE unused React component
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
package com.ddf.vodsystem.security;
|
||||
|
||||
import com.ddf.vodsystem.entities.User;
|
||||
import lombok.Getter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class CustomOAuth2User implements OAuth2User {
|
||||
|
||||
private final OAuth2User oauth2User;
|
||||
private final User user;
|
||||
|
||||
public CustomOAuth2User(OAuth2User oauth2User, User user) {
|
||||
this.oauth2User = oauth2User;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getAttributes() {
|
||||
return oauth2User.getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return oauth2User.getAuthorities();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return oauth2User.getName();
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.ddf.vodsystem.security;
|
||||
import com.ddf.vodsystem.entities.User;
|
||||
import com.ddf.vodsystem.repositories.UserRepository;
|
||||
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@Service
|
||||
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public CustomOAuth2UserService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CustomOAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
var delegate = new DefaultOAuth2UserService();
|
||||
var oAuth2User = delegate.loadUser(userRequest);
|
||||
|
||||
String email = oAuth2User.getAttribute("email");
|
||||
String name = oAuth2User.getAttribute("name");
|
||||
String googleId = oAuth2User.getAttribute("sub");
|
||||
|
||||
User user = userRepository.findByGoogleId(googleId)
|
||||
.orElseGet(() -> {
|
||||
User newUser = new User();
|
||||
newUser.setEmail(email);
|
||||
newUser.setName(name);
|
||||
newUser.setGoogleId(googleId);
|
||||
newUser.setUsername(email);
|
||||
newUser.setRole(0);
|
||||
newUser.setCreatedAt(LocalDateTime.now());
|
||||
return userRepository.save(newUser);
|
||||
});
|
||||
|
||||
return new CustomOAuth2User(oAuth2User, user);
|
||||
}
|
||||
}
|
||||
90
src/main/java/com/ddf/vodsystem/security/JwtFilter.java
Normal file
90
src/main/java/com/ddf/vodsystem/security/JwtFilter.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package com.ddf.vodsystem.security;
|
||||
|
||||
import com.ddf.vodsystem.entities.User;
|
||||
import com.ddf.vodsystem.exceptions.NotAuthenticated;
|
||||
import com.ddf.vodsystem.services.UserService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class JwtFilter extends OncePerRequestFilter {
|
||||
private final JwtService jwtService;
|
||||
private final UserService userService;
|
||||
private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
|
||||
|
||||
public JwtFilter(JwtService jwtService,
|
||||
UserService userService) {
|
||||
this.jwtService = jwtService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
String jwt = null;
|
||||
|
||||
// 1. Try to get the JWT from the Authorization header
|
||||
String authorizationHeader = request.getHeader("Authorization");
|
||||
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
|
||||
logger.debug("JWT found in Authorization header");
|
||||
jwt = authorizationHeader.substring(7);
|
||||
}
|
||||
|
||||
// 2. If no JWT was found in the header, try to get it from a cookie
|
||||
if (jwt == null) {
|
||||
logger.debug("JWT not found in Authorization header, checking cookies");
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
jwt = Arrays.stream(cookies)
|
||||
.filter(cookie -> "token".equals(cookie.getName()))
|
||||
.map(Cookie::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (jwt == null) {
|
||||
logger.debug("No JWT found in request");
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("JWT found in request");
|
||||
Long userId = jwtService.validateTokenAndGetUserId(jwt);
|
||||
|
||||
if (userId == null) {
|
||||
logger.warn("Invalid JWT: {}", jwt);
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
User user;
|
||||
try {
|
||||
user = userService.getUserById(userId);
|
||||
} catch (NotAuthenticated e) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(user, jwt, List.of(new SimpleGrantedAuthority("ROLE_USER")));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
54
src/main/java/com/ddf/vodsystem/security/JwtService.java
Normal file
54
src/main/java/com/ddf/vodsystem/security/JwtService.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package com.ddf.vodsystem.security;
|
||||
|
||||
import java.util.Date;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.exceptions.TokenExpiredException;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private final long jwtExpiration;
|
||||
private final Algorithm algorithm; // Algorithm is now initialized after secret key is available
|
||||
|
||||
private static final String USER_ID_CLAIM = "userId";
|
||||
private static final String ISSUER = "vodsystem";
|
||||
|
||||
public JwtService(@Value("${jwt.secret.key}") String jwtSecretKey,
|
||||
@Value("${jwt.expiration}") long jwtExpiration) {
|
||||
this.jwtExpiration = jwtExpiration;
|
||||
this.algorithm = Algorithm.HMAC256(jwtSecretKey);
|
||||
}
|
||||
|
||||
public String generateToken(Long userId) {
|
||||
return JWT.create()
|
||||
.withClaim(USER_ID_CLAIM, userId)
|
||||
.withIssuer(ISSUER)
|
||||
.withExpiresAt(new Date(System.currentTimeMillis() + jwtExpiration))
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
public Long validateTokenAndGetUserId(String token) {
|
||||
try {
|
||||
JWTVerifier verifier = JWT.require(algorithm)
|
||||
.withClaimPresence(USER_ID_CLAIM)
|
||||
.withIssuer(ISSUER)
|
||||
.build();
|
||||
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
|
||||
if (jwt.getExpiresAt() == null || jwt.getExpiresAt().before(new Date())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return jwt.getClaim(USER_ID_CLAIM).asLong();
|
||||
} catch (JwtException | IllegalArgumentException | TokenExpiredException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user