This commit is contained in:
Constantin Simonis 2025-03-06 12:52:15 +01:00
parent f797092d38
commit 1ea610d423
8 changed files with 84 additions and 28 deletions

View file

@ -48,7 +48,7 @@ dependencies {
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.springframework.boot:spring-boot-starter-security") 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-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") runtimeOnly("org.postgresql:postgresql")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0") implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
} }

View file

@ -47,13 +47,13 @@ class KeycloakSecurityConfig {
@Bean @Bean
public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception { public SecurityFilterChain resourceServerFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth return http.authorizeHttpRequests(authz -> {
.requestMatchers("/swagger", "/swagger-ui/**", "/v3/api-docs/**", "/health").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(spec -> spec.jwt(Customizer.withDefaults()));
return http.build(); authz.requestMatchers("/swagger/**", "/v3/api-docs/**", "/swagger-ui/**", "/health")
.permitAll()
.anyRequest()
.authenticated();
}).build();
} }
@Bean @Bean

View file

@ -8,16 +8,23 @@ app.frontend-host=http://localhost:4200
spring.application.name=lf12_starter spring.application.name=lf12_starter
#client registration configuration #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.authentik.provider=authentik
spring.security.oauth2.client.registration.keycloak.scope=openid 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: #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 logging.level.org.springframework.security=DEBUG
#validating JWT token against our Keycloak server #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.path=swagger
springdoc.swagger-ui.try-it-out-enabled=true springdoc.swagger-ui.try-it-out-enabled=true

View file

@ -3,15 +3,16 @@ import { provideRouter } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { routes } from './app.routes'; 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 { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideOAuthClient } from 'angular-oauth2-oidc'; import { provideOAuthClient } from 'angular-oauth2-oidc';
import { httpInterceptor } from './shared/interceptor/http.interceptor';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideRouter(routes), provideRouter(routes),
FontAwesomeModule, FontAwesomeModule,
provideHttpClient(), provideHttpClient(withInterceptors([httpInterceptor])),
provideExperimentalZonelessChangeDetection(), provideExperimentalZonelessChangeDetection(),
provideAnimationsAsync(), provideAnimationsAsync(),
provideOAuthClient(), provideOAuthClient(),

View file

@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { AuthService } from '../../service/auth.service'; import { AuthService } from '../../service/auth.service';
import { User } from '../../model/User';
@Component({ @Component({
selector: 'app-login-success', selector: 'app-login-success',
@ -14,7 +15,6 @@ export default class LoginSuccessComponent implements OnInit {
private router: Router = inject(Router); private router: Router = inject(Router);
private authService: AuthService = inject(AuthService); private authService: AuthService = inject(AuthService);
async ngOnInit() { async ngOnInit() {
console.log(this.authService.getAccessToken()); this.authService.getUserInfo()
(this.authService.getUserInfo().then(console.log));
} }
} }

View file

@ -1,29 +1,47 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc'; import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { UserService } from './user.service';
import { User } from '../model/User';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class AuthService { export class AuthService {
private userService: UserService = inject(UserService);
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/',
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', clientId: 'MDqjm1kcWKuZfqHJXjxwAV20i44aT7m4VhhTL3Nm',
redirectUri: window.location.origin + '/auth/callback', dummyClientSecret: 'GY2F8te6iAVYt1TNAUVLzWZEXb6JoMNp6chbjqaXNq4gS5xTDL54HqBiAlV1jFKarN28LQ7FUsYX4SbwjfEhZhgeoKuBnZKjR9eiu7RawnGgxIK9ffvUfMkjRxnmiGI5',
responseType: 'code',
scope: 'openid profile email', scope: 'openid profile email',
showDebugInformation: true, responseType: 'code',
redirectUri: window.location.origin + '/auth/callback',
oidc: true, oidc: true,
requestAccessToken: true, requestAccessToken: true,
strictDiscoveryDocumentValidation: false,
showDebugInformation: true,
}; };
private isAuthenticated = new Subject<boolean>(); private isAuthenticated = new Subject<boolean>();
private user: User | null = null;
private oauthService: OAuthService = inject(OAuthService); private oauthService: OAuthService = inject(OAuthService);
constructor() { constructor() {
this.oauthService.configure(this.authConfig); 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.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => {
this.isAuthenticated.next(this.oauthService.hasValidAccessToken()); this.isAuthenticated.next(this.oauthService.hasValidAccessToken());
}); });
@ -38,15 +56,19 @@ export class AuthService {
this.isAuthenticated.next(false); this.isAuthenticated.next(false);
} }
getAccessToken() {
return this.oauthService.getAccessToken();
}
getUserInfo() { getUserInfo() {
return this.oauthService.loadUserProfile(); return this.user;
} }
isLoggedIn() { isLoggedIn() {
return this.oauthService.hasValidAccessToken(); return this.oauthService.hasValidAccessToken();
} }
private fromUserProfile(profile: object) {
return this.userService.getOrCreateUser(profile);
}
getAccessToken() {
return this.oauthService.getAccessToken();
}
} }

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 } from 'rxjs'; import { catchError, EMPTY, Observable, of, switchMap } from 'rxjs';
import { User } from '../model/User'; import { User } from '../model/User';
@Injectable({ @Injectable({
@ -23,4 +23,21 @@ export class UserService {
username: username, username: username,
}); });
} }
public getOrCreateUser(profile: any): Observable<User> {
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)
);
}
} }

View file

@ -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()},
}));
};