У меня есть Angular SPA-приложение, которое я хочу интегрировать с сервером идентификации 4.
Я пытаюсь использовать новый рекомендуемый поток аутентификации, используя angular -auth-oid c -client library.
Я установил перехватчик Angular, который запускается при каждом запросе и пытается получить токен путем принудительного входа в систему, если пользователь не аутентифицирован. Его код такой:
import { HttpInterceptor, HttpRequest, HttpEvent, HttpHandler} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthService } from '../authentication/auth.service';
@Injectable()
export class TokenInterceptorService implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
console.log('INTERCEPTOR' + this.authService);
//// We retrieve the token, if any
let token = this.authService.getToken();
let newHeaders = req.headers;
if (!token) {
this.authService.login();
}
token = this.authService.getToken();
newHeaders = newHeaders.append('bearer', token);
//// Finally we have to clone our request with our new headers
//// This is required because HttpRequests are immutable
const authReq = req.clone({headers: newHeaders});
//// Then we return an Observable that will run the request
//// or pass it to the next interceptor if any
return next.handle(authReq);
}
}
Реализация AuthService выглядит следующим образом
import { Injectable, OnDestroy, Inject } from '@angular/core';
import { OidcSecurityService, OpenIdConfiguration, AuthWellKnownEndpoints, AuthorizationResult, AuthorizationState } from 'angular-auth-oidc-client';
import { Observable , Subscription, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { HttpHeaders, HttpClient, HttpParams } from '@angular/common/http';
import { Router } from '@angular/router';
@Injectable()
export class AuthService implements OnDestroy {
isAuthorized = false;
public bearerToken: string | null = null;
public sessionState: number | null = null;
constructor(
private oidcSecurityService: OidcSecurityService,
private http: HttpClient,
private router: Router,
@Inject('BASE_URL') private originUrl: string,
@Inject('AUTH_URL') private authUrl: string,
) {
}
private isAuthorizedSubscription: Subscription = new Subscription;
ngOnDestroy(): void {
if (this.isAuthorizedSubscription) {
this.isAuthorizedSubscription.unsubscribe();
}
}
public initAuth() {
const openIdConfiguration: OpenIdConfiguration = {
stsServer: this.authUrl,
redirect_url: this.originUrl + 'callback',
client_id: 'myApp.api',
response_type: 'code',
scope: 'myApp.api',
post_logout_redirect_uri: this.originUrl,
forbidden_route: '/forbidden',
unauthorized_route: '/unauthorized',
silent_renew: true,
silent_renew_url: this.originUrl,
history_cleanup_off: true,
auto_userinfo: true,
log_console_warning_active: true,
log_console_debug_active: true,
max_id_token_iat_offset_allowed_in_seconds: 10,
};
const authWellKnownEndpoints: AuthWellKnownEndpoints = {
issuer:'.office.sbs',
jwks_uri: this.authUrl + '/.well-known/openid-configuration/jwks',
authorization_endpoint: this.authUrl + '/connect/authorize',
token_endpoint: this.authUrl + '/connect/token',
userinfo_endpoint: this.authUrl + '/connect/userinfo',
end_session_endpoint: this.authUrl + '/connect/endsession',
check_session_iframe: this.authUrl + '/connect/checksession',
revocation_endpoint: this.authUrl + '/connect/revocation',
introspection_endpoint: this.authUrl + '/connect/introspect',
};
this.oidcSecurityService.setupModule(openIdConfiguration, authWellKnownEndpoints);
if (this.oidcSecurityService.moduleSetup) {
this.doCallbackLogicIfRequired();
} else {
this.oidcSecurityService.onModuleSetup.subscribe(() => {
this.doCallbackLogicIfRequired();
});
}
this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe((isAuthorized => {
this.isAuthorized = isAuthorized;
}));
this.oidcSecurityService.onAuthorizationResult.subscribe(
(authorizationResult: AuthorizationResult) => {
this.onAuthorizationResultComplete(authorizationResult);
});
}
private onAuthorizationResultComplete(authorizationResult: AuthorizationResult) {
console.log('Auth result received AuthorizationState:'
+ authorizationResult.authorizationState
+ ' validationResult:' + authorizationResult.validationResult);
if (authorizationResult.authorizationState === AuthorizationState.unauthorized) {
if (window.parent) {
// sent from the child iframe, for example the silent renew
this.router.navigate(['/unauthorized']);
} else {
window.location.href = '/unauthorized';
}
}
else if (authorizationResult.authorizationState === AuthorizationState.authorized) {
console.log("authenticated");
}
}
private doCallbackLogicIfRequired() {
//this.oidcSecurityService.authorizedCallbackWithCode(window.location.toString());
console.log(window.location);
const urlParts = window.location.toString().split('?');
const params = new HttpParams({
fromString: urlParts[1]
});
const code = params.get('code');
const state = params.get('state');
if (code && state && this.sessionState === null) {
this.sessionState = Math.random() * 50000;
this.oidcSecurityService.requestTokensWithCode(code, state, this.sessionState.toString());
}
}
getIsAuthorized(): Observable<boolean> {
return this.oidcSecurityService.getIsAuthorized();
}
login() {
console.log('start login');
this.oidcSecurityService.authorize();
}
logout() {
console.log('start logoff');
this.oidcSecurityService.logoff();
}
get(url: string): Observable<any> {
return this.http.get(url, { headers: this.getHeaders() })
.pipe(catchError((error) => {
this.oidcSecurityService.handleError(error);
return throwError(error);
}));
}
put(url: string, data: any): Observable<any> {
const body = JSON.stringify(data);
return this.http.put(url, body, { headers: this.getHeaders() })
.pipe(catchError((error) => {
this.oidcSecurityService.handleError(error);
return throwError(error);
}));
}
delete(url: string): Observable<any> {
return this.http.delete(url, { headers: this.getHeaders() })
.pipe(catchError((error) => {
this.oidcSecurityService.handleError(error);
return throwError(error);
}));
}
post(url: string, data: any): Observable<any> {
const body = JSON.stringify(data);
return this.http.post(url, body, { headers: this.getHeaders() })
.pipe(catchError((error) => {
this.oidcSecurityService.handleError(error);
return throwError(error);
}));
}
private getHeaders() {
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/json');
return this.appendAuthHeader(headers);
}
public getToken() {
const token = this.oidcSecurityService.getToken();
return token;
}
private appendAuthHeader(headers: HttpHeaders) {
const token = this.oidcSecurityService.getToken();
if (token === '') { return headers; }
const tokenValue = 'Bearer ' + token;
return headers.set('Authorization', tokenValue);
}
}
Основная проблема заключается в том, что когда я нажимаю на запрос с токеном в angular -auth- oid c -client. js (код ниже), запросы продолжают переворачиваться между сервером идентификации и страницей обратного вызова, и код продолжает выполнять метод doCallbackLogicIfRequired.
OidcSecurityService.prototype.requestTokensWithCodeProcedure$ =
// Code Flow with PCKE
/**
* @param {?} code
* @param {?} state
* @param {?} session_state
* @return {?}
*/
function (code, state, session_state) {
var _this = this;
/** @type {?} */
var tokenRequestUrl = '';
if (this.configurationProvider.wellKnownEndpoints && this.configurationProvider.wellKnownEndpoints.token_endpoint) {
tokenRequestUrl = "" + this.configurationProvider.wellKnownEndpoints.token_endpoint;
}
if (!this.oidcSecurityValidation.validateStateFromHashCallback(state, this.oidcSecurityCommon.authStateControl)) {
this.loggerService.logWarning('authorizedCallback incorrect state');
// ValidationResult.StatesDoNotMatch;
return throwError(new Error('incorrect state'));
}
/** @type {?} */
var headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
/** @type {?} */
var data = oneLineTrim(templateObject_1 || (templateObject_1 = __makeTemplateObject(["grant_type=authorization_code&client_id=", "\n &code_verifier=", "\n &code=", "&redirect_uri=", ""], ["grant_type=authorization_code&client_id=", "\n &code_verifier=", "\n &code=", "&redirect_uri=", ""])), this.configurationProvider.openIDConfiguration.client_id, this.oidcSecurityCommon.code_verifier, code, this.configurationProvider.openIDConfiguration.redirect_url);
if (this.oidcSecurityCommon.silentRenewRunning === 'running') {
data = oneLineTrim(templateObject_2 || (templateObject_2 = __makeTemplateObject(["grant_type=authorization_code&client_id=", "\n &code_verifier=", "\n &code=", "\n &redirect_uri=", ""], ["grant_type=authorization_code&client_id=", "\n &code_verifier=", "\n &code=", "\n &redirect_uri=", ""])), this.configurationProvider.openIDConfiguration.client_id, this.oidcSecurityCommon.code_verifier, code, this.configurationProvider.openIDConfiguration.silent_renew_url);
}
return this.httpClient.post(tokenRequestUrl, data, { headers: headers }).pipe(map((/**
* @param {?} response
* @return {?}
*/
function (response) {
/** @type {?} */
var obj = new Object();
obj = response;
obj.state = state;
obj.session_state = session_state;
_this.authorizedCodeFlowCallbackProcedure(obj);
return undefined;
})), catchError((/**
* @param {?} error
* @return {?}
*/
function (error) {
_this.loggerService.logError(error);
_this.loggerService.logError("OidcService code request " + _this.configurationProvider.openIDConfiguration.stsServer);
return throwError(error);
})));
};
Проверяя консоль Журнал, я вижу, я получил токен
VM1604 app.bundle.js:11364 INTERCEPTOR[object Object]
VM1604 app.bundle.js:1527 start login
VM1604 app.bundle.js:89392 BEGIN Authorize Code Flow, no auth data
VM1604 app.bundle.js:89392 AuthorizedController created. local state: 15821982471040.47561711387714610.5390697281765799
VM1604 app.bundle.js:75476 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
VM1604 app.bundle.js:89392 BEGIN authorized Code Flow Callback, no auth data
VM1604 app.bundle.js:89392 history clean up inactive
VM1604 app.bundle.js:89392 {access_token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IkJFODdGRjgwRjhBMkQyMT…V9-uKxgRq46JSiVpZbUSm_d7GB07aO1k4SvRWmIqw20Fo7ADA", expires_in: 3600, token_type: "Bearer", state: "15821982471040.47561711387714610.5390697281765799", session_state: "45871.95561094084"}
VM1604 app.bundle.js:89392 authorizedCallback created, begin token validation
VM1604 app.bundle.js:89392 jwks_uri: https://mySecurityMachine.myDomain/authentication/.well-known/openid-configuration/jwks
VM1604 app.bundle.js:11364 INTERCEPTOR[object Object]
VM1604 app.bundle.js:1527 start login
VM1604 app.bundle.js:89392 BEGIN Authorize Code Flow, no auth data
VM1604 app.bundle.js:89392 AuthorizedController created. local state: 15821982471040.47561711387714610.5390697281765799
log.js:24 [HMR] Waiting for update signal from WDS...
log.js:24 [HMR] Waiting for update signal from WDS...
Navigated to https://localhost.myDomain:8083/callback?code=b30915bfb9a0f8ea838b685b467fd33c02c3e3282210e9ca11807665a8116d87&scope=cc.api&state=15821982471040.47561711387714610.5390697281765799
log.js:24 [HMR] Waiting for update signal from WDS...
angular-auth-oidc-client.js:392 STS server: https://mySecurityMachine.myDomain/authentication
angular-auth-oidc-client.js:392 Silent Renew is active, check if token in storage is active
angular-auth-oidc-client.js:392 <iframe id="myiFrameForSilentRenew" style="display: none;">…</iframe>
auth.service.ts:105 Location {href: "https://localhost.myDomain:8083/callback?code=b3…15821982471040.47561711387714610.5390697281765799", ancestorOrigins: DOMStringList, origin: "https://localhost.myDomain:8083", protocol: "https:", host: "localhost.myDomain:8083", …}
Как показано в
VM1604 app.bundle.js:89392 {access_token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IkJFODdGRjgwRjhBMkQyMT…V9-uKxgRq46JSiVpZbUSm_d7GB07aO1k4SvRWmIqw20Fo7ADA", expires_in: 3600, token_type: "Bearer", state: "15821982471040.47561711387714610.5390697281765799", session_state: "45871.95561094084"}
Что мне не хватает, чтобы перенаправить на нужную страницу?