wip
This commit is contained in:
parent
f797092d38
commit
1ea610d423
8 changed files with 84 additions and 28 deletions
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
9
frontend/src/app/shared/interceptor/http.interceptor.ts
Normal file
9
frontend/src/app/shared/interceptor/http.interceptor.ts
Normal 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()},
|
||||||
|
}));
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue