Authorization with Keycloak
This commit is contained in:
parent
9289e486b1
commit
c060acc7db
11 changed files with 120 additions and 25 deletions
|
@ -16,3 +16,8 @@
|
|||
1. Docker Container Hochfahren
|
||||
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-dynamic": "^19.0.0",
|
||||
"@angular/router": "^19.0.0",
|
||||
"angular-auth-oidc-client": "^19.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
|
@ -5955,6 +5956,22 @@
|
|||
"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": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
|
@ -13545,6 +13562,12 @@
|
|||
"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": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
"@angular/platform-browser": "^19.0.0",
|
||||
"@angular/platform-browser-dynamic": "^19.0.0",
|
||||
"@angular/router": "^19.0.0",
|
||||
"angular-auth-oidc-client": "^19.0.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Component} from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
import {HeaderComponent} from '@app/header/header.component';
|
||||
|
||||
@Component({
|
||||
|
@ -8,5 +8,5 @@ import {HeaderComponent} from '@app/header/header.component';
|
|||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent{
|
||||
export class AppComponent {
|
||||
}
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from '@app/app.routes';
|
||||
import { OpenAPI } from '@core/ems/core/OpenAPI';
|
||||
import {provideHttpClient} from '@angular/common/http';
|
||||
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
||||
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {routes} from '@app/app.routes';
|
||||
import {OpenAPI} from '@core/ems/core/OpenAPI';
|
||||
import {LogLevel, provideAuth} from 'angular-auth-oidc-client';
|
||||
|
||||
OpenAPI.BASE = 'http://localhost:8080';
|
||||
OpenAPI.TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzUFQ0dldiNno5MnlQWk1EWnBqT1U0RjFVN0lwNi1ELUlqQWVGczJPbGU0In0.eyJleHAiOjE3MzQ1MjA4MzgsImlhdCI6MTczNDUxNzIzOCwianRpIjoiODVkM2NlNDQtM2QwMS00MDFkLTlhY2YtZjFhZTMwNzQzMDRmIiwiaXNzIjoiaHR0cHM6Ly9rZXljbG9hay5zenV0LmRldi9hdXRoL3JlYWxtcy9zenV0IiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjU1NDZjZDIxLTk4NTQtNDMyZi1hNDY3LTRkZTNlZWRmNTg4OSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImVtcGxveWVlLW1hbmFnZW1lbnQtc2VydmljZSIsInNlc3Npb25fc3RhdGUiOiJiOWI3OGQ0ZS1jM2E4LTRiOWQtODdmMy04N2FiNzhjYzQyYTMiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NDIwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsicHJvZHVjdF9vd25lciIsIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1zenV0IiwidW1hX2F1dGhvcml6YXRpb24iLCJ1c2VyIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6InVzZXIifQ.I3CGELu7IXrh5kYapp-v54c-R8hNqZOtvpmR98NhoTCrOVUoA-V16ayhF42qQ3Pj2YymxENpQWHR1-BTFQOJmAhQKBzvKWgOHuiLCPr8NkMGWFQ520BcJdipbGnsM8tmXuLE9FWezHA7LEGVkwY2gTbQMLEPq8v8hrtIf76F2Dq34BIG3Nq_6QlG10rG_Heqta6kWWe2p4DJmebdpvAeW2qvTLD4x89oIhtYXSiRIiVS2uAur5XuJcjpbv8UXWx11zVX7KelZEXky92q_xPKerFMOGt6up5MkCijJBhVWewSWNldaix7_jKTXCp70Gtu4sgCIuoojKp9Nts942RZjw';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideZoneChangeDetection({eventCoalescing: true}),
|
||||
provideAnimationsAsync(),
|
||||
provideRouter(routes),
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,25 @@
|
|||
<mat-toolbar class="header">
|
||||
<nav>
|
||||
<a routerLink="'/'" mat-button><mat-icon>badge</mat-icon>EMS</a>
|
||||
@for (route of routes; track route){
|
||||
<a routerLink="'/'" mat-button>
|
||||
<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>
|
||||
}
|
||||
</nav>
|
||||
<button mat-button class="header__login">
|
||||
<mat-icon>login</mat-icon>
|
||||
Login
|
||||
</button>
|
||||
|
||||
|
||||
@if (auth.$user|async; as user) {
|
||||
<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>
|
|
@ -1,9 +1,10 @@
|
|||
import {Location} from '@angular/common';
|
||||
import {AsyncPipe, Location, TitleCasePipe} from '@angular/common';
|
||||
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 {MatToolbar} from '@angular/material/toolbar';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {AuthService} from '@core/auth/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-header',
|
||||
|
@ -13,6 +14,9 @@ import {Router, RouterLink} from '@angular/router';
|
|||
RouterLink,
|
||||
MatIcon,
|
||||
MatButton,
|
||||
MatIconButton,
|
||||
AsyncPipe,
|
||||
TitleCasePipe,
|
||||
],
|
||||
templateUrl: './header.component.html',
|
||||
styleUrl: './header.component.scss'
|
||||
|
@ -22,7 +26,8 @@ export class HeaderComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private location: Location
|
||||
private location: Location,
|
||||
protected auth: AuthService
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue