- Added JwtAuthenticationFilter
- Added UploadHistory - Added secure endpoints to SecurityConfig
This commit is contained in:
parent
59e0fb1b48
commit
5f681a7a1b
@ -1,32 +1,45 @@
|
|||||||
package de.w665.sharepulse.config;
|
package de.w665.sharepulse.config;
|
||||||
|
|
||||||
|
import de.w665.sharepulse.rest.security.JwtAuthenticationFilter;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
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.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
|
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.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||||
|
|
||||||
|
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
|
||||||
|
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
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
|
.authorizeHttpRequests(authorize -> authorize
|
||||||
.requestMatchers("/api/v1/secure/**").authenticated()
|
.requestMatchers("/api/v1/secure/**").authenticated() // Secure these endpoints
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll() // All other requests are allowed without authentication
|
||||||
)
|
|
||||||
.formLogin(formLogin -> formLogin
|
|
||||||
.loginPage("/management/login")
|
|
||||||
.permitAll()
|
|
||||||
)
|
)
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // Apply JWT filter
|
||||||
.logout(LogoutConfigurer::permitAll)
|
.logout(LogoutConfigurer::permitAll)
|
||||||
.csrf(csrf -> csrf
|
|
||||||
.ignoringRequestMatchers("/api/v1/**") // Disable CSRF for /api/**
|
|
||||||
)
|
|
||||||
.rememberMe(Customizer.withDefaults());
|
.rememberMe(Customizer.withDefaults());
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Fix the security configuration to allow public access to unsecured endpoints
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -57,9 +57,10 @@ public class AuthenticationService {
|
|||||||
Date expiryDate = new Date(nowMillis + expirationTime);
|
Date expiryDate = new Date(nowMillis + expirationTime);
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.subject(username.getUsername())
|
.subject("SharePulse Authentication Token")
|
||||||
.issuedAt(now)
|
.issuedAt(now)
|
||||||
.claim("role", username.getRole())
|
.claim("role", username.getRole())
|
||||||
|
.claim("username", username.getUsername())
|
||||||
.expiration(expiryDate)
|
.expiration(expiryDate)
|
||||||
.signWith(secretKey)
|
.signWith(secretKey)
|
||||||
.compact();
|
.compact();
|
||||||
@ -83,8 +84,7 @@ public class AuthenticationService {
|
|||||||
* Retrieves a typed claim from the JWT.
|
* Retrieves a typed claim from the JWT.
|
||||||
* @param token the JWT from which to extract the claim
|
* @param token the JWT from which to extract the claim
|
||||||
* @param claimName the name of the claim to retrieve
|
* @param claimName the name of the claim to retrieve
|
||||||
* @param claimType the Class object of the type T of the claim
|
* @param claimType the Class object of <T> the expected type of the claim value
|
||||||
* @param <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
|
* @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)
|
* Usage example: getClaimValue(token, "role", String.class)
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user