From 29338353e2de538becadf0e717e12d39f8fdf2fe Mon Sep 17 00:00:00 2001 From: Constantin Simonis Date: Thu, 6 Mar 2025 12:52:15 +0100 Subject: [PATCH] wip --- backend/build.gradle.kts | 2 +- .../security/KeycloakSecurityConfig.java | 14 +++---- .../src/main/resources/application.properties | 19 ++++++--- frontend/src/app/app.config.ts | 5 ++- .../login-success/login-success.component.ts | 4 +- frontend/src/app/service/auth.service.ts | 40 ++++++++++++++----- frontend/src/app/service/user.service.ts | 19 ++++++++- .../shared/interceptor/http.interceptor.ts | 9 +++++ 8 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 frontend/src/app/shared/interceptor/http.interceptor.ts diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index a2060f7..36ecee7 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -48,7 +48,7 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-launcher") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.3.3") - implementation("org.springframework.boot:spring-boot-starter-oauth2-client:3.3.3") + implementation("org.springframework.boot:spring-boot-starter-oauth2-client") runtimeOnly("org.postgresql:postgresql") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") } diff --git a/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java b/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java index 6be8b83..02ff8d9 100644 --- a/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java +++ b/backend/src/main/java/de/szut/casino/security/KeycloakSecurityConfig.java @@ -47,13 +47,13 @@ class KeycloakSecurityConfig { @Bean public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests(auth -> auth - .requestMatchers("/swagger", "/swagger-ui/**", "/v3/api-docs/**", "/health").permitAll() - .anyRequest().authenticated() - ) - .oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults())); - - return http.build(); + return http.authorizeHttpRequests(authz -> { + + authz.requestMatchers("/swagger/**", "/v3/api-docs/**", "/swagger-ui/**", "/health") + .permitAll() + .anyRequest() + .authenticated(); + }).build(); } @Bean diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index c7ccadf..356045f 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -8,16 +8,23 @@ app.frontend-host=http://localhost:4200 spring.application.name=lf12_starter #client registration configuration -spring.security.oauth2.client.registration.keycloak.client-id=lf12 -spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code -spring.security.oauth2.client.registration.keycloak.scope=openid + +spring.security.oauth2.client.registration.authentik.provider=authentik +spring.security.oauth2.client.registration.authentik.client-id=MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm +spring.security.oauth2.client.registration.authentik.client-secret=GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5 +spring.security.oauth2.client.registration.authentik.redirect-uri={baseUrl}/login/oauth2/code/{registrationId} +spring.security.oauth2.client.registration.authentik.scope=openid, profile, email +spring.security.oauth2.client.registration.authentik.client-name=Authentik +spring.security.oauth2.client.registration.authentik.authorization-grant-type=authorization_code +spring.security.oauth2.client.provider.authentik.authorization-uri=https://oauth.simonis.lol/application/o/authorize/ +spring.security.oauth2.client.provider.authentik.issuer-uri=https://oauth.simonis.lol/ +spring.security.oauth2.client.provider.authentik.token-uri=https://oauth.simonis.lol/application/o/token/ +spring.security.oauth2.client.provider.authentik.user-info-uri=https://oauth.simonis.lol/application/o/userinfo/ +spring.security.oauth2.client.provider.authentik.jwk-set-uri=https://oauth.simonis.lol/application/o/jwks/ #OIDC provider configuration: -spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:9090/realms/LF12 -spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username logging.level.org.springframework.security=DEBUG #validating JWT token against our Keycloak server -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9090/realms/LF12 springdoc.swagger-ui.path=swagger springdoc.swagger-ui.try-it-out-enabled=true diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 4d755cd..d786629 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -3,15 +3,16 @@ import { provideRouter } from '@angular/router'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { routes } from './app.routes'; -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideOAuthClient } from 'angular-oauth2-oidc'; +import { httpInterceptor } from './shared/interceptor/http.interceptor'; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), FontAwesomeModule, - provideHttpClient(), + provideHttpClient(withInterceptors([httpInterceptor])), provideExperimentalZonelessChangeDetection(), provideAnimationsAsync(), provideOAuthClient(), diff --git a/frontend/src/app/feature/login-success/login-success.component.ts b/frontend/src/app/feature/login-success/login-success.component.ts index 06727b4..a305ad7 100644 --- a/frontend/src/app/feature/login-success/login-success.component.ts +++ b/frontend/src/app/feature/login-success/login-success.component.ts @@ -1,6 +1,7 @@ import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from '../../service/auth.service'; +import { User } from '../../model/User'; @Component({ selector: 'app-login-success', @@ -14,7 +15,6 @@ export default class LoginSuccessComponent implements OnInit { private router: Router = inject(Router); private authService: AuthService = inject(AuthService); async ngOnInit() { - console.log(this.authService.getAccessToken()); - (this.authService.getUserInfo().then(console.log)); + this.authService.getUserInfo() } } diff --git a/frontend/src/app/service/auth.service.ts b/frontend/src/app/service/auth.service.ts index 9b128a0..fc94c3a 100644 --- a/frontend/src/app/service/auth.service.ts +++ b/frontend/src/app/service/auth.service.ts @@ -1,29 +1,47 @@ import { inject, Injectable } from '@angular/core'; import { Subject } from 'rxjs'; import { AuthConfig, OAuthService } from 'angular-oauth2-oidc'; +import { UserService } from './user.service'; +import { User } from '../model/User'; @Injectable({ providedIn: 'root', }) export class AuthService { + private userService: UserService = inject(UserService); + private readonly authConfig: AuthConfig = { issuer: 'https://oauth.simonis.lol/application/o/casino-dev/', + loginUrl: 'https://oauth.simonis.lol/application/o/authorize/', + tokenEndpoint: 'https://oauth.simonis.lol/application/o/token/', + userinfoEndpoint: 'https://oauth.simonis.lol/application/o/userinfo/', clientId: 'MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm', - redirectUri: window.location.origin + '/auth/callback', - responseType: 'code', + dummyClientSecret: 'GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5', scope: 'openid profile email', - showDebugInformation: true, + responseType: 'code', + redirectUri: window.location.origin + '/auth/callback', oidc: true, requestAccessToken: true, + strictDiscoveryDocumentValidation: false, + showDebugInformation: true, }; private isAuthenticated = new Subject(); - + private user: User | null = null; private oauthService: OAuthService = inject(OAuthService); constructor() { this.oauthService.configure(this.authConfig); + this.oauthService.events.subscribe((event) => { + if (event.type === 'token_received') { + this.oauthService.loadUserProfile().then((profile) => { + this.fromUserProfile(profile).subscribe((user) => { + this.user = user; + }); + }); + } + }); this.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => { this.isAuthenticated.next(this.oauthService.hasValidAccessToken()); }); @@ -38,15 +56,19 @@ export class AuthService { this.isAuthenticated.next(false); } - getAccessToken() { - return this.oauthService.getAccessToken(); - } - getUserInfo() { - return this.oauthService.loadUserProfile(); + return this.user; } isLoggedIn() { return this.oauthService.hasValidAccessToken(); } + + private fromUserProfile(profile: object) { + return this.userService.getOrCreateUser(profile); + } + + getAccessToken() { + return this.oauthService.getAccessToken(); + } } diff --git a/frontend/src/app/service/user.service.ts b/frontend/src/app/service/user.service.ts index 9f34b8a..e574fa4 100644 --- a/frontend/src/app/service/user.service.ts +++ b/frontend/src/app/service/user.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { catchError, EMPTY, Observable } from 'rxjs'; +import { catchError, EMPTY, Observable, of, switchMap } from 'rxjs'; import { User } from '../model/User'; @Injectable({ @@ -23,4 +23,21 @@ export class UserService { username: username, }); } + + public getOrCreateUser(profile: any): Observable { + console.log(profile); + const id = profile.info.sub; + const username = profile.info.preferred_username; + + return this.getUser(id).pipe( + switchMap((user) => { + if (user) { + return of(user); + } else { + return this.createUser(id, username); + } + }), + catchError(() => EMPTY) + ); + } } diff --git a/frontend/src/app/shared/interceptor/http.interceptor.ts b/frontend/src/app/shared/interceptor/http.interceptor.ts new file mode 100644 index 0000000..a62c3b1 --- /dev/null +++ b/frontend/src/app/shared/interceptor/http.interceptor.ts @@ -0,0 +1,9 @@ +import { HttpInterceptorFn } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { AuthService } from '../../service/auth.service'; + +export const httpInterceptor: HttpInterceptorFn = (req, next) => { + return next(req.clone({ + setHeaders: {'Authorization': 'Bearer '+inject(AuthService).getAccessToken()}, + })); +};