Updated auth config
Some checks failed
Gitea/swiss-backend/pipeline/head There was a failure building this commit

This commit is contained in:
2025-10-08 21:36:05 +02:00
parent f46c619be3
commit 46662ac553
9 changed files with 208 additions and 11 deletions

View File

@@ -31,6 +31,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>

View File

@@ -0,0 +1,44 @@
package nl.connectedit.swiss.auth;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.stream.Collectors;
@RestController
@CrossOrigin
@RequestMapping("/api/auth")
public class AuthController {
private final JwtEncoder encoder;
public AuthController(JwtEncoder encoder) {
this.encoder = encoder;
}
@PostMapping
public String auth(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plusSeconds(expiry))
.subject(authentication.getName())
.claim("scope", scope)
.build();
return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
}

View File

@@ -0,0 +1,9 @@
package nl.connectedit.swiss.auth;
public final class Roles {
public static final String USER = "USER";
private Roles() {
}
}

View File

@@ -1,26 +1,148 @@
package nl.connectedit.swiss.config; package nl.connectedit.swiss.config;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.DispatcherType; import jakarta.servlet.DispatcherType;
import nl.connectedit.swiss.auth.Roles;
import org.springframework.beans.factory.annotation.Value;
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.http.HttpMethod;
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.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
//@Configuration import java.security.interfaces.RSAPrivateKey;
//@EnableWebSecurity import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig { public class SecurityConfig {
// @Bean private final RSAPublicKey key;
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { private final RSAPrivateKey priv;
public SecurityConfig(@Value("${jwt.public.key}") RSAPublicKey key,
@Value("${jwt.private.key}") RSAPrivateKey priv) {
this.key = key;
this.priv = priv;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http
.csrf(AbstractHttpConfigurer::disable) .cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()); .csrf(csrf -> csrf.ignoringRequestMatchers("/api/auth"))
// .authorizeHttpRequests(request -> { .authorizeHttpRequests(authorize -> authorize
// request.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll(); // Allow OPTIONS requests for CORS preflight
// request.requestMatchers("/error").permitAll(); .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// }); // Allow POST to /api/auth without JWT (Basic Auth will still be required)
.requestMatchers(HttpMethod.POST, "/api/auth").permitAll()
// All other requests require authentication
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
return http.build(); return http.build();
} }
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Allow your Angular app's origin(s)
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:4200",
"http://localhost:8080",
// Add your production URL here when deploying
"https://badminton-toernooi.nl",
"https://test.badminton-toernooi.nl"
));
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
));
configuration.setAllowedHeaders(Arrays.asList(
"Authorization",
"Content-Type",
"Accept"
));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
UserDetailsService users() {
return new InMemoryUserDetailsManager(
User.withUsername("bcholten")
.password(passwordEncoder().encode("bcholten"))
.roles(Roles.USER)
.build()
);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// Remove the SCOPE_ prefix
grantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
} }

View File

@@ -1,6 +1,8 @@
package nl.connectedit.swiss.controller; package nl.connectedit.swiss.controller;
import jakarta.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.entity.Player; import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.dto.PlayerDto; import nl.connectedit.swiss.dto.PlayerDto;
import nl.connectedit.swiss.mapper.PlayerMapper; import nl.connectedit.swiss.mapper.PlayerMapper;
@@ -12,6 +14,7 @@ import java.util.List;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class PlayerController { public class PlayerController {

View File

@@ -1,6 +1,8 @@
package nl.connectedit.swiss.controller; package nl.connectedit.swiss.controller;
import jakarta.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.TournamentStatus; import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.dto.TournamentRegistrationDto; import nl.connectedit.swiss.dto.TournamentRegistrationDto;
import nl.connectedit.swiss.mapper.TournamentPlayerRegistrationMapper; import nl.connectedit.swiss.mapper.TournamentPlayerRegistrationMapper;
@@ -20,6 +22,7 @@ import java.util.List;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class RegistrationController { public class RegistrationController {

View File

@@ -3,10 +3,12 @@ package nl.connectedit.swiss.controller;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.RolesAllowed;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.*; import nl.connectedit.swiss.domain.*;
import nl.connectedit.swiss.domain.entity.Player; import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.entity.Registration; import nl.connectedit.swiss.domain.entity.Registration;
@@ -39,6 +41,7 @@ import static nl.connectedit.swiss.domain.PlayerStrength.*;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class TestController { public class TestController {

View File

@@ -1,7 +1,9 @@
package nl.connectedit.swiss.controller; package nl.connectedit.swiss.controller;
import jakarta.annotation.security.RolesAllowed;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.TournamentStatus; import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.Tournament; import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.dto.ResultDto; import nl.connectedit.swiss.dto.ResultDto;
@@ -20,6 +22,7 @@ import java.util.Objects;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class TournamentController { public class TournamentController {

View File

@@ -30,4 +30,10 @@ management:
probes: probes:
enabled: true enabled: true
security: true security: true
jwt:
private:
key: classpath:certs/private.pem
public:
key: classpath:certs/public.pem