Authorization with Keycloak
This commit is contained in:
parent
c846650556
commit
886bcde0c3
11 changed files with 120 additions and 25 deletions
|
@ -16,3 +16,8 @@
|
||||||
1. Docker Container Hochfahren
|
1. Docker Container Hochfahren
|
||||||
1. Angular App starten
|
1. Angular App starten
|
||||||
|
|
||||||
|
## Zugangsdaten
|
||||||
|
|
||||||
|
**Benutzername:** `user`
|
||||||
|
**Passwort:** `test`
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
POST http://authproxy.szut.dev
|
|
||||||
Content-Type: application/x-www-form-urlencoded
|
|
||||||
|
|
||||||
grant_type=password&client_id=employee-management-service&username=user&password=test
|
|
23
package-lock.json
generated
23
package-lock.json
generated
|
@ -18,6 +18,7 @@
|
||||||
"@angular/platform-browser": "^19.0.0",
|
"@angular/platform-browser": "^19.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^19.0.0",
|
"@angular/platform-browser-dynamic": "^19.0.0",
|
||||||
"@angular/router": "^19.0.0",
|
"@angular/router": "^19.0.0",
|
||||||
|
"angular-auth-oidc-client": "^19.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
|
@ -5955,6 +5956,22 @@
|
||||||
"ajv": "^8.8.2"
|
"ajv": "^8.8.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/angular-auth-oidc-client": {
|
||||||
|
"version": "19.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/angular-auth-oidc-client/-/angular-auth-oidc-client-19.0.0.tgz",
|
||||||
|
"integrity": "sha512-CloBjmHjG6CxbFFjYB1Ei+e172JUY1V3cK/v9pdbVuUz3OhiMC6CxBr331oB3Em2eWMmLi23jecl10lfiy9WUQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"rfc4648": "^1.5.0",
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": ">=15.0.0",
|
||||||
|
"@angular/core": ">=15.0.0",
|
||||||
|
"@angular/router": ">=15.0.0",
|
||||||
|
"rxjs": "^6.5.3 || ^7.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ansi-colors": {
|
"node_modules/ansi-colors": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||||
|
@ -13545,6 +13562,12 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rfc4648": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/rfdc": {
|
"node_modules/rfdc": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"@angular/platform-browser": "^19.0.0",
|
"@angular/platform-browser": "^19.0.0",
|
||||||
"@angular/platform-browser-dynamic": "^19.0.0",
|
"@angular/platform-browser-dynamic": "^19.0.0",
|
||||||
"@angular/router": "^19.0.0",
|
"@angular/router": "^19.0.0",
|
||||||
|
"angular-auth-oidc-client": "^19.0.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.15.0"
|
"zone.js": "~0.15.0"
|
||||||
|
@ -54,4 +55,4 @@
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.12.0"
|
"node": "22.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import { RouterOutlet } from '@angular/router';
|
import {RouterOutlet} from '@angular/router';
|
||||||
import {HeaderComponent} from '@app/header/header.component';
|
import {HeaderComponent} from '@app/header/header.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -8,5 +8,5 @@ import {HeaderComponent} from '@app/header/header.component';
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss'
|
styleUrl: './app.component.scss'
|
||||||
})
|
})
|
||||||
export class AppComponent{
|
export class AppComponent {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
import { provideHttpClient } from '@angular/common/http';
|
import {provideHttpClient} from '@angular/common/http';
|
||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
||||||
import { provideRouter } from '@angular/router';
|
import {provideRouter} from '@angular/router';
|
||||||
import { routes } from '@app/app.routes';
|
import {routes} from '@app/app.routes';
|
||||||
import { OpenAPI } from '@core/ems/core/OpenAPI';
|
import {OpenAPI} from '@core/ems/core/OpenAPI';
|
||||||
|
import {LogLevel, provideAuth} from 'angular-auth-oidc-client';
|
||||||
|
|
||||||
OpenAPI.BASE = 'http://localhost:8080';
|
OpenAPI.BASE = 'http://localhost:8080';
|
||||||
OpenAPI.TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzUFQ0dldiNno5MnlQWk1EWnBqT1U0RjFVN0lwNi1ELUlqQWVGczJPbGU0In0.eyJleHAiOjE3MzQ1MjA4MzgsImlhdCI6MTczNDUxNzIzOCwianRpIjoiODVkM2NlNDQtM2QwMS00MDFkLTlhY2YtZjFhZTMwNzQzMDRmIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5zenV0LmRldi9hdXRoL3JlYWxtcy9zenV0IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjU1NDZjZDIxLTk4NTQtNDMyZi1hNDY3LTRkZTNlZWRmNTg4OSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImVtcGxveWVlLW1hbmFnZW1lbnQtc2VydmljZSIsInNlc3Npb25fc3RhdGUiOiJiOWI3OGQ0ZS1jM2E4LTRiOWQtODdmMy04N2FiNzhjYzQyYTMiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NDIwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsicHJvZHVjdF9vd25lciIsIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1zenV0IiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIifQ.I3CGELu7IXrh5kYapp-v54c-R8hNqZOtvpmR98NhoTCrOVUoA-V16ayhF42qQ3Pj2YymxENpQWHR1-BTFQOJmAhQKBzvKWgOHuiLCPr8NkMGWFQ520BcJdipbGnsM8tmXuLE9FWezHA7LEGVkwY2gTbQMLEPq8v8hrtIf76F2Dq34BIG3Nq_6QlG10rG_Heqta6kWWe2p4DJmebdpvAeW2qvTLD4x89oIhtYXSiRIiVS2uAur5XuJcjpbv8UXWx11zVX7KelZEXky92q_xPKerFMOGt6up5MkCijJBhVWewSWNldaix7_jKTXCp70Gtu4sgCIuoojKp9Nts942RZjw';
|
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({eventCoalescing: true}),
|
||||||
|
provideAnimationsAsync(),
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideHttpClient(),
|
provideHttpClient(),
|
||||||
provideAnimationsAsync(), provideAnimationsAsync()
|
provideAuth({
|
||||||
|
config: {
|
||||||
|
authority: 'https://keycloak.szut.dev/auth/realms/szut',
|
||||||
|
redirectUrl: window.location.origin,
|
||||||
|
postLogoutRedirectUri: window.location.origin,
|
||||||
|
clientId: 'employee-management-service',
|
||||||
|
scope: 'openid profile email offline_access',
|
||||||
|
responseType: 'code',
|
||||||
|
silentRenew: true,
|
||||||
|
useRefreshToken: true,
|
||||||
|
logLevel: LogLevel.Error,
|
||||||
|
},
|
||||||
|
})
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
6
src/app/core/auth/UserData.d.ts
vendored
Normal file
6
src/app/core/auth/UserData.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
interface UserData {
|
||||||
|
username: string,
|
||||||
|
verified: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserData;
|
32
src/app/core/auth/auth.service.ts
Normal file
32
src/app/core/auth/auth.service.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import UserData from '@core/auth/UserData';
|
||||||
|
import {OidcSecurityService} from 'angular-auth-oidc-client';
|
||||||
|
import {Observable} from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthService {
|
||||||
|
public $user: Observable<UserData | undefined>;
|
||||||
|
|
||||||
|
constructor(private readonly oidcSecurityService: OidcSecurityService) {
|
||||||
|
this.$user = new Observable((publish) => {
|
||||||
|
this.oidcSecurityService.checkAuth().subscribe(({isAuthenticated, userData}) => {
|
||||||
|
publish.next(isAuthenticated ? {
|
||||||
|
username: userData.preferred_username,
|
||||||
|
verified: userData.email_verified
|
||||||
|
} : undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
this.oidcSecurityService.authorize();
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.oidcSecurityService.logoff().subscribe((result) => console.log(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,12 +1,25 @@
|
||||||
<mat-toolbar class="header">
|
<mat-toolbar class="header">
|
||||||
<nav>
|
<nav>
|
||||||
<a routerLink="'/'" mat-button><mat-icon>badge</mat-icon>EMS</a>
|
<a routerLink="'/'" mat-button>
|
||||||
@for (route of routes; track route){
|
<mat-icon>badge</mat-icon>
|
||||||
|
EMS</a>
|
||||||
|
@for (route of routes; track route) {
|
||||||
<a mat-button href="{{ route.path }}" class="{{ route.class }}">{{ route.title }}</a>
|
<a mat-button href="{{ route.path }}" class="{{ route.class }}">{{ route.title }}</a>
|
||||||
}
|
}
|
||||||
</nav>
|
</nav>
|
||||||
<button mat-button class="header__login">
|
|
||||||
<mat-icon>login</mat-icon>
|
|
||||||
Login
|
@if (auth.$user|async; as user) {
|
||||||
</button>
|
<a mat-button href="https://keycloak.szut.dev/auth/realms/szut/account">
|
||||||
|
{{ user.username|titlecase }}
|
||||||
|
</a>
|
||||||
|
<button mat-icon-button (click)="auth.logout()">
|
||||||
|
<mat-icon>logout</mat-icon>
|
||||||
|
</button>
|
||||||
|
} @else {
|
||||||
|
<button mat-button class="header__login" (click)="auth.login()">
|
||||||
|
<mat-icon>login</mat-icon>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
}
|
||||||
</mat-toolbar>
|
</mat-toolbar>
|
|
@ -1,9 +1,10 @@
|
||||||
import {Location} from '@angular/common';
|
import {AsyncPipe, Location, TitleCasePipe} from '@angular/common';
|
||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {MatAnchor, MatButton} from '@angular/material/button';
|
import {MatAnchor, MatButton, MatIconButton} from '@angular/material/button';
|
||||||
import {MatIcon} from '@angular/material/icon';
|
import {MatIcon} from '@angular/material/icon';
|
||||||
import {MatToolbar} from '@angular/material/toolbar';
|
import {MatToolbar} from '@angular/material/toolbar';
|
||||||
import {Router, RouterLink} from '@angular/router';
|
import {Router, RouterLink} from '@angular/router';
|
||||||
|
import {AuthService} from '@core/auth/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
|
@ -13,6 +14,9 @@ import {Router, RouterLink} from '@angular/router';
|
||||||
RouterLink,
|
RouterLink,
|
||||||
MatIcon,
|
MatIcon,
|
||||||
MatButton,
|
MatButton,
|
||||||
|
MatIconButton,
|
||||||
|
AsyncPipe,
|
||||||
|
TitleCasePipe,
|
||||||
],
|
],
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrl: './header.component.scss'
|
styleUrl: './header.component.scss'
|
||||||
|
@ -22,7 +26,8 @@ export class HeaderComponent implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private location: Location
|
private location: Location,
|
||||||
|
protected auth: AuthService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue