feat(security): add CORS support and update security config
Some checks failed
CI / Get Changed Files (pull_request) Successful in 9s
CI / eslint (pull_request) Failing after 29s
CI / prettier (pull_request) Failing after 32s
CI / test-build (pull_request) Failing after 58s
CI / Checkstyle Main (pull_request) Successful in 1m24s

This commit is contained in:
Constantin Simonis 2025-03-26 13:27:42 +01:00
parent 242b72ca45
commit 3da534f3ae
No known key found for this signature in database
GPG key ID: 3878FF77C24AF4D2
11 changed files with 53 additions and 49 deletions

View file

@ -5,6 +5,12 @@ import org.springframework.context.annotation.Configuration;
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.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;
import java.util.Arrays;
import java.util.List;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@ -12,13 +18,29 @@ public class SecurityConfig {
@Bean @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> { http
auth.requestMatchers("/swagger/**", "/health").permitAll() .authorizeHttpRequests(auth -> {
.requestMatchers("/").authenticated(); auth.requestMatchers("/swagger/**", "/swagger-ui/**", "/health").permitAll()
}) .anyRequest().authenticated();
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter()))); })
.oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt ->
jwt.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter())
));
return http.build(); return http.build();
} }
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
configuration.setExposedHeaders(List.of("x-auth-token"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
} }

View file

@ -5,7 +5,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
@ -23,20 +22,13 @@ public class UserController {
@Autowired @Autowired
private UserService userService; private UserService userService;
@GetMapping("/user/{id}")
public ResponseEntity<?> getUser(@PathVariable String id) {
if (id == null || !userService.exists(id)) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(userService.getUser(id));
}
@PostMapping("/user") @PostMapping("/user")
public ResponseEntity<?> createUser(@RequestBody @Valid CreateUserDto userData) { public ResponseEntity<?> createUser(@RequestBody @Valid CreateUserDto userData) {
if (userService.exists(userData.getKeycloakId())) { if (userService.exists(userData.getAuthentikId())) {
HttpHeaders headers = new HttpHeaders();
headers.add("Location", "/user");
return this.redirect("/user/" + userData.getKeycloakId()); return new ResponseEntity<>(headers, HttpStatus.FOUND);
} }
return ResponseEntity.ok(userService.createUser(userData)); return ResponseEntity.ok(userService.createUser(userData));
@ -52,11 +44,4 @@ public class UserController {
return ResponseEntity.ok(userData); return ResponseEntity.ok(userData);
} }
private ResponseEntity<Object> redirect(String route) {
HttpHeaders headers = new HttpHeaders();
headers.add("Location", route);
return new ResponseEntity<>(headers, HttpStatus.FOUND);
}
} }

View file

@ -13,6 +13,6 @@ public class UserMappingService {
} }
public UserEntity mapToUserEntity(CreateUserDto createUserDto) { public UserEntity mapToUserEntity(CreateUserDto createUserDto) {
return new UserEntity(createUserDto.getKeycloakId(), createUserDto.getUsername(), BigDecimal.ZERO); } return new UserEntity(createUserDto.getAuthentikId(), createUserDto.getUsername(), BigDecimal.ZERO); }
} }

View file

@ -1,7 +1,8 @@
package de.szut.casino.user; package de.szut.casino.user;
import java.util.Optional; import de.szut.casino.user.dto.CreateUserDto;
import de.szut.casino.user.dto.GetUserDto;
import de.szut.casino.user.dto.KeycloakUserDto;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -10,9 +11,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import de.szut.casino.user.dto.CreateUserDto; import java.util.Optional;
import de.szut.casino.user.dto.GetUserDto;
import de.szut.casino.user.dto.KeycloakUserDto;
@Service @Service
public class UserService { public class UserService {
@ -53,7 +52,7 @@ public class UserService {
private KeycloakUserDto getKeycloakUserInfo(String token) { private KeycloakUserDto getKeycloakUserInfo(String token) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token); headers.set("Authorization", token);
ResponseEntity<KeycloakUserDto> response = this.http.exchange("http://localhost:9090/realms/LF12/protocol/openid-connect/userinfo", HttpMethod.GET, new HttpEntity<>(headers), KeycloakUserDto.class); ResponseEntity<KeycloakUserDto> response = this.http.exchange("https://oauth.simonis.lol/application/o/userinfo/", HttpMethod.GET, new HttpEntity<>(headers), KeycloakUserDto.class);
return response.getBody(); return response.getBody();
} }

View file

@ -10,6 +10,6 @@ import lombok.Setter;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class CreateUserDto { public class CreateUserDto {
private String keycloakId; private String authentikId;
private String username; private String username;
} }

View file

@ -12,7 +12,7 @@ import java.math.BigDecimal;
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class GetUserDto { public class GetUserDto {
private String keycloakId; private String authentikId;
private String username; private String username;
private BigDecimal balance; private BigDecimal balance;
} }

View file

@ -13,6 +13,7 @@ spring.application.name=lf12_starter
spring.security.oauth2.client.registration.authentik.client-id=MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm spring.security.oauth2.client.registration.authentik.client-id=MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm
spring.security.oauth2.client.registration.authentik.client-secret=GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5 spring.security.oauth2.client.registration.authentik.client-secret=GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://oauth.simonis.lol/application/o/casino-dev/ spring.security.oauth2.resourceserver.jwt.issuer-uri=https://oauth.simonis.lol/application/o/casino-dev/
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://oauth.simonis.lol/application/o/casino-dev/jwks/
#OIDC provider configuration: #OIDC provider configuration:
logging.level.org.springframework.security=DEBUG logging.level.org.springframework.security=DEBUG

View file

@ -36,7 +36,7 @@ export const appConfig: ApplicationConfig = {
provideOAuthClient(), provideOAuthClient(),
{ {
provide: OAuthStorage, provide: OAuthStorage,
useFactory: () => storageFactory(), useFactory: () => localStorage,
} }
], ],
}; };

View file

@ -4,6 +4,7 @@ import { AuthConfig, OAuthService, OAuthStorage } from 'angular-oauth2-oidc';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { User } from '../model/User'; import { User } from '../model/User';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { environment } from '../../environments/environment';
@Injectable({ @Injectable({
@ -12,9 +13,9 @@ import { Router } from '@angular/router';
export class AuthService { export class AuthService {
private readonly authConfig: AuthConfig = { private readonly authConfig: AuthConfig = {
issuer: 'https://oauth.simonis.lol/application/o/casino-dev/', issuer: 'https://oauth.simonis.lol/application/o/casino-dev/',
clientId: 'MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm', clientId: environment.OAUTH_CLIENT_ID,
dummyClientSecret: 'GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5', dummyClientSecret: environment.OAUTH_CLIENT_SECRET,
scope: 'openid profile email', scope: `openid email profile ${environment.OAUTH_CLIENT_ID}`,
responseType: 'code', responseType: 'code',
redirectUri: window.location.origin + '/auth/callback', redirectUri: window.location.origin + '/auth/callback',
oidc: true, oidc: true,
@ -22,6 +23,9 @@ export class AuthService {
strictDiscoveryDocumentValidation: false, strictDiscoveryDocumentValidation: false,
skipIssuerCheck: true, skipIssuerCheck: true,
disableAtHashCheck: true, disableAtHashCheck: true,
jwks: {
skipJwksValidation: true,
}
}; };
private userService: UserService = inject(UserService); private userService: UserService = inject(UserService);

View file

@ -1,6 +1,6 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { catchError, EMPTY, Observable, of, switchMap } from 'rxjs'; import { catchError, EMPTY, Observable } from 'rxjs';
import { User } from '../model/User'; import { User } from '../model/User';
@Injectable({ @Injectable({
@ -9,10 +9,6 @@ import { User } from '../model/User';
export class UserService { export class UserService {
private http: HttpClient = inject(HttpClient); private http: HttpClient = inject(HttpClient);
public getUser(id: string): Observable<User | null> {
return this.http.get<User | null>(`/backend/user/${id}`).pipe(catchError(() => EMPTY));
}
public getCurrentUser(): Observable<User | null> { public getCurrentUser(): Observable<User | null> {
return this.http.get<User | null>('/backend/user').pipe(catchError(() => EMPTY)); return this.http.get<User | null>('/backend/user').pipe(catchError(() => EMPTY));
} }
@ -27,10 +23,6 @@ export class UserService {
public getOrCreateUser(profile: any): Observable<User> { public getOrCreateUser(profile: any): Observable<User> {
const id = profile.info.sub; const id = profile.info.sub;
const username = profile.info.preferred_username; const username = profile.info.preferred_username;
try { return this.createUser(id, username);
return this.getUser(id) as Observable<User>;
} catch (error) {
return this.createUser(id, username);
}
} }
} }

View file

@ -1,4 +1,5 @@
export const environment = { export const environment = {
STRIPE_KEY: STRIPE_KEY: 'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG',
'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG', OAUTH_CLIENT_ID: 'MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm',
OAUTH_CLIENT_SECRET: 'GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5'
}; };