- Added JwtAuthenticationFilter

- Added UploadHistory
- Added secure endpoints to SecurityConfig
This commit is contained in:
Max W. 2024-05-19 22:12:44 +02:00
parent 59e0fb1b48
commit 5f681a7a1b
4 changed files with 119 additions and 12 deletions

View File

@ -1,32 +1,45 @@
package de.w665.sharepulse.config;
import de.w665.sharepulse.rest.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.ignoringRequestMatchers("/api/v1/**")) // Disable CSRF for API routes
.sessionManagement(sessionManagement -> sessionManagement
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No session will be created by Spring Security
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/v1/secure/**").authenticated()
.anyRequest().permitAll()
)
.formLogin(formLogin -> formLogin
.loginPage("/management/login")
.permitAll()
.requestMatchers("/api/v1/secure/**").authenticated() // Secure these endpoints
.anyRequest().permitAll() // All other requests are allowed without authentication
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Apply JWT filter
.logout(LogoutConfigurer::permitAll)
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/v1/**") // Disable CSRF for /api/**
)
.rememberMe(Customizer.withDefaults());
return http.build();
}
}
// TODO: Fix the security configuration to allow public access to unsecured endpoints

View File

@ -0,0 +1,20 @@
package de.w665.sharepulse.rest.mappings;
import de.w665.sharepulse.rest.SecureApiRestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class UploadHistory extends SecureApiRestController {
@GetMapping("/test")
public ResponseEntity<Object> test(HttpServletRequest request) {
log.debug("Received test request");
return ResponseEntity.ok("Test successful");
}
}

View File

@ -0,0 +1,74 @@
package de.w665.sharepulse.rest.security;
import de.w665.sharepulse.service.AuthenticationService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final AuthenticationService authenticationService;
private final RequestMatcher requestMatcher = new AntPathRequestMatcher("/api/v1/secure/**");
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
logger.debug("Filtering request: " + request.getRequestURI());
if(!requestMatcher.matches(request)) {
logger.debug("Request does not match the secure path. Skipping JWT authentication.");
filterChain.doFilter(request, response);
return;
}
try {
String jwt = getJwtFromRequest(request);
if (jwt != null && authenticationService.validateToken(jwt)) {
String username = authenticationService.extractSubject(jwt);
// Extract the role from the JWT and set it to Spring AuthenticationContext for access control
String role = authenticationService.getClaimValue(jwt, "role", String.class);
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role));
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
// SUCCESSFUL AUTHENTICATION
filterChain.doFilter(request, response);
} else {
logger.warn("Unauthorized: Authentication token is missing or invalid.");
}
} catch (Exception ex) {
logger.warn("Could not set user authentication in security context. An error occurred during JWT processing.", ex);
}
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}

View File

@ -57,9 +57,10 @@ public class AuthenticationService {
Date expiryDate = new Date(nowMillis + expirationTime);
return Jwts.builder()
.subject(username.getUsername())
.subject("SharePulse Authentication Token")
.issuedAt(now)
.claim("role", username.getRole())
.claim("username", username.getUsername())
.expiration(expiryDate)
.signWith(secretKey)
.compact();
@ -83,8 +84,7 @@ public class AuthenticationService {
* Retrieves a typed claim from the JWT.
* @param token the JWT from which to extract the claim
* @param claimName the name of the claim to retrieve
* @param claimType the Class object of the type T of the claim
* @param <T> the expected type of the claim value
* @param claimType the Class object of <T> the expected type of the claim value
* @return the value of the specified claim as type T, or null if not found or in case of an error
* Usage example: getClaimValue(token, "role", String.class)
*/