使用代码身份验证流陷入身份服务器的重定向循环

时间:2020-02-20 12:22:26

标签: angular single-page-application identityserver4

我有一个要与身份服务器4集成的Angular SPA应用程序。

我正在尝试通过angular-auth-oidc-client库使用新的推荐身份验证流程。

我已经设置了一个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-oidc-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"}

要重定向到所需页面我缺少什么?

2 个答案:

答案 0 :(得分:0)

尝试这个受欢迎的客户angular-oauth2-oidc 有一些说明,一切都开始起作用。 此外,该软件包还包含一个很棒的authservice,您将不再需要authservice

答案 1 :(得分:0)

我最终要做的是使用

将请求网址存储在TokenInterceptorService中的本地存储中
inv$start_year_month <- factor(inv$start_year_month, levels=paste(unique(format(inv$Month_Due, "%Y")), month.name, sep="-"))

我将redirectUri配置为将我重定向到myApp / callback端点,在该端点上component.ts类检查本地存储上是否设置了URL值。如果有,它将重定向到它

private currentUrlIndex = 'UrlBeforeLoginRedirect';
console.log("After the login will redirect to " + window.location.href);
if(this.getFromLocalStorage(this.currentUrlIndex) == null)
    this.setOnLocalStorage(this.currentUrlIndex, window.location.href);

this.authService.login();
let tokenFromNewAuthentication = this.authService.getToken();
resultingToken = tokenFromNewAuthentication;

我喜欢这个解决方案,因为它重定向到最初请求的url(读取特定记录),而不是像烘焙解决方案那样重定向到首页。