feat(security): add CORS support and update security config
Some checks failed
Some checks failed
This commit is contained in:
parent
242b72ca45
commit
3da534f3ae
11 changed files with 53 additions and 49 deletions
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const appConfig: ApplicationConfig = {
|
||||||
provideOAuthClient(),
|
provideOAuthClient(),
|
||||||
{
|
{
|
||||||
provide: OAuthStorage,
|
provide: OAuthStorage,
|
||||||
useFactory: () => storageFactory(),
|
useFactory: () => localStorage,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue