无服务器API网关AWS_IAM Angular 5签名请求

时间:2018-03-28 22:56:40

标签: javascript angular amazon-web-services aws-api-gateway amazon-cognito

我正在开发一个Angular 5项目,该项目具有无服务器API,需要使用AWS_IAM进行身份验证。

所以有点背景。我们使用Cognito Federated Identities来发布临时凭证。 Cognito正在使用我们的AD FS环境进行联合。

当我使用Cogntio提供的临时凭证尝试通过邮递员调用我们的API时,它会成功生成标题,因此问题必须在我们的Angular应用程序中。 https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-use-postman-to-call-api.html

我将无服务器堆栈sigV4Client改为Angular服务。因为我以前在React项目中成功使用它。

https://serverless-stack.com/chapters/connect-to-api-gateway-with-iam-auth.html https://raw.githubusercontent.com/AnomalyInnovations/sigV4Client/master/sigV4Client.js

我的角色服务:

import {Injectable} from "@angular/core";
import * as SHA256 from 'crypto-js/sha256';
import * as encHex from 'crypto-js/enc-hex';
import * as HmacSHA256 from 'crypto-js/hmac-sha256';

@Injectable()
export class awsSigV4Service {

    private AWS_SHA_256: string = "AWS4-HMAC-SHA256";
    private AWS4_REQUEST: string = "aws4_request";
    private AWS4: string = "AWS4";
    private X_AMZ_DATE: string = "x-amz-date";
    private X_AMZ_SECURITY_TOKEN: string = "x-amz-security-token";
    private HOST: string = "host";
    private AUTHORIZATION: string = "Authorization";

    public awsSigV4Client = {
        accessKey: null,
        secretKey: null,
        sessionToken: null,
        serviceName: null,
        region: null,
        defaultAcceptType: null,
        defaultContentType: null,
        endpoint: null,
        pathComponent: null,
    };

    constructor(config) {

        if (config.accessKey === undefined || config.secretKey === undefined) {
            return this;
        }

        this.awsSigV4Client.accessKey = config.accessKey;
        this.awsSigV4Client.secretKey = config.secretKey;
        this.awsSigV4Client.sessionToken = config.sessionToken;
        this.awsSigV4Client.serviceName = config.serviceName || "execute-api";
        this.awsSigV4Client.region = config.region || "us-east-1";
        this.awsSigV4Client.defaultAcceptType = config.defaultAcceptType || "application/json";
        this.awsSigV4Client.defaultContentType = config.defaultContentType || "application/json";

        let invokeUrl = config.endpoint;
        let endpoint = /(^https?:\/\/[^/]+)/g.exec(invokeUrl)[1];
        let pathComponent = invokeUrl.substring(endpoint.length);

        this.awsSigV4Client.endpoint = endpoint;
        this.awsSigV4Client.pathComponent = pathComponent;

        return this;
    }

    hash(value) {
        return SHA256(value); // eslint-disable-line
    }

    hexEncode(value) {
        return value.toString(encHex);
    }

    hmac(secret, value) {
        return HmacSHA256(value, secret, {asBytes: true}); // eslint-disable-line
    }

    buildCanonicalRequest(method, path, queryParams, headers, payload) {

        let canonicalUri = this.buildCanonicalUri(path);
        let canonicalQueryString = this.buildCanonicalQueryString(queryParams);
        let canonicalHeaders = this.buildCanonicalHeaders(headers);
        let canonicalSignedHeaders = this.buildCanonicalSignedHeaders(headers);
        let hex = this.hexEncode(this.hash(payload));

        return (
            method +
            "\n" +
            canonicalUri +
            "\n" +
            canonicalQueryString +
            "\n" +
            canonicalHeaders +
            "\n" +
            canonicalSignedHeaders +
            "\n" +
            hex
        );
    }

    hashCanonicalRequest(request) {
        return this.hexEncode(this.hash(request));
    }

    buildCanonicalUri(uri) {
        return encodeURI(uri);
    }

    buildCanonicalQueryString(queryParams) {
        if (Object.keys(queryParams).length < 1) {
            return "";
        }

        let sortedQueryParams = [];
        for (let property in queryParams) {
            if (queryParams.hasOwnProperty(property)) {
                sortedQueryParams.push(property);
            }
        }
        sortedQueryParams.sort();

        let canonicalQueryString = "";
        for (let i = 0; i < sortedQueryParams.length; i++) {
            canonicalQueryString +=
                sortedQueryParams[i] +
                "=" +
                encodeURIComponent(queryParams[sortedQueryParams[i]]) +
                "&";
        }
        return canonicalQueryString.substr(0, canonicalQueryString.length - 1);
    }

    buildCanonicalHeaders(headers) {
        let canonicalHeaders = "";
        let sortedKeys = [];
        for (let property in headers) {
            if (headers.hasOwnProperty(property)) {
                sortedKeys.push(property);
            }
        }
        sortedKeys.sort();

        for (let i = 0; i < sortedKeys.length; i++) {
            canonicalHeaders +=
                sortedKeys[i].toLowerCase() + ":" + headers[sortedKeys[i]] + "\n";
        }
        return canonicalHeaders;
    }

    buildCanonicalSignedHeaders(headers) {
        let sortedKeys = [];
        for (let property in headers) {
            if (headers.hasOwnProperty(property)) {
                sortedKeys.push(property.toLowerCase());
            }
        }
        sortedKeys.sort();

        return sortedKeys.join(";");
    }

    buildStringToSign(datetime,
                      credentialScope,
                      hashedCanonicalRequest) {
        return (
            this.AWS_SHA_256 +
            "\n" +
            datetime +
            "\n" +
            credentialScope +
            "\n" +
            hashedCanonicalRequest
        );
    }

    buildCredentialScope(datetime, region, service) {
        return (
            datetime.substr(0, 8) + "/" + region + "/" + service + "/" + this.AWS4_REQUEST
        );
    }

    calculateSigningKey(secretKey, datetime, region, service) {
        return this.hmac(
            this.hmac(
                this.hmac(this.hmac(this.AWS4 + secretKey, datetime.substr(0, 8)), region),
                service
            ),
            this.AWS4_REQUEST
        );
    }

    calculateSignature(key, stringToSign) {
        return this.hexEncode(this.hmac(key, stringToSign));
    }

    extractHostname(url) {
        let hostname;

        if (url.indexOf("://") > -1) {
            hostname = url.split('/')[2];
        }
        else {
            hostname = url.split('/')[0];
        }

        hostname = hostname.split(':')[0];
        hostname = hostname.split('?')[0];

        return hostname;
    }

    buildAuthorizationHeader(accessKey,
                             credentialScope,
                             headers,
                             signature) {
        return (
            this.AWS_SHA_256 +
            " Credential=" +
            accessKey +
            "/" +
            credentialScope +
            ", SignedHeaders=" +
            this.buildCanonicalSignedHeaders(headers) +
            ", Signature=" +
            signature
        );
    }

    signRequest(request) {
        const verb = request.method.toUpperCase();
        const path = this.awsSigV4Client.pathComponent + request.path;
        const queryParams = {...request.queryParams};
        const headers = {...request.headers};

        // If the user has not specified an override for Content type the use default
        if (headers["Content-Type"] === undefined) {
            headers["Content-Type"] = this.awsSigV4Client.defaultContentType;
        }

        // If the user has not specified an override for Accept type the use default
        if (headers["Accept"] === undefined) {
            headers["Accept"] = this.awsSigV4Client.defaultAcceptType;
        }

        let body = {...request.body};

        // override request body and set to empty when signing GET requests
        if (request.body === undefined || verb === "GET") {
            body = "";
        } else {
            body = JSON.stringify(body);
        }

// If there is no body remove the content-type header so it is not
// included in SigV4 calculation
        if (body === "" || body === undefined || body === null) {
            delete headers["Content-Type"];
        }

        let datetime = new Date()
            .toISOString()
            .replace(/\.\d{3}Z$/, "Z")
            .replace(/[:-]|\.\d{3}/g, "");
        headers[this.X_AMZ_DATE] = datetime;
        headers[this.HOST] = this.extractHostname(this.awsSigV4Client.endpoint);

        let canonicalRequest = this.buildCanonicalRequest(
            verb,
            path,
            queryParams,
            headers,
            body
        );
        let hashedCanonicalRequest = this.hashCanonicalRequest(canonicalRequest);
        let credentialScope = this.buildCredentialScope(
            datetime,
            this.awsSigV4Client.region,
            this.awsSigV4Client.serviceName
        );
        let stringToSign = this.buildStringToSign(
            datetime,
            credentialScope,
            hashedCanonicalRequest
        );
        let signingKey = this.calculateSigningKey(
            this.awsSigV4Client.secretKey,
            datetime,
            this.awsSigV4Client.region,
            this.awsSigV4Client.serviceName
        );
        let signature = this.calculateSignature(signingKey, stringToSign);
        headers[this.AUTHORIZATION] = this.buildAuthorizationHeader(
            this.awsSigV4Client.accessKey,
            credentialScope,
            headers,
            signature
        );
        if (
            this.awsSigV4Client.sessionToken !== undefined &&
            this.awsSigV4Client.sessionToken !== ""
        ) {
            headers[this.X_AMZ_SECURITY_TOKEN] = this.awsSigV4Client.sessionToken;
        }
        delete headers[this.HOST];

        let url = this.awsSigV4Client.endpoint + path;
        let queryString = this.buildCanonicalQueryString(queryParams);
        if (queryString !== "") {
            url += "?" + queryString;
        }

        // Need to re-attach Content-Type if it is not specified at this point
        if (headers["Content-Type"] === undefined) {
            headers["Content-Type"] = this.awsSigV4Client.defaultContentType;
        }

        return {
            headers: headers,
            url: url
        };
    }

}

服务的实施

import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
import { IStudent, fromServer } from './model';
import { IAppState } from '../model';
import { awsSigV4Service } from '../../service/awsSigV4.service';

import { environment } from "../../../environments/environment";
const AWS = require("aws-sdk");

@Injectable()
export class StudentAPIService {
  constructor(
      private http: Http
  ) { }

  get = (studentId: string): Observable<IStudent> => {

    let config = {
      accessKey: AWS.config.credentials.accessKeyId,
      secretKey: AWS.config.credentials.secretAccessKey,
      sessionToken: AWS.config.credentials.sessionToken,
      region: environment.region,
      endpoint: environment.khub_api_endpoint
    };

    let request = {
      method: "GET",
      path: "/ep/student",
      headers: {},
      queryParams: {
        StudentNo: studentId
      },
      body: null
    };

    let signedRequest = new awsSigV4Service(config).signRequest(request);

    console.log('config', config);
    console.log('signedRequest', signedRequest);

    debugger;
    
    //TODO: CALL the endpoint
  };

  put = (student: IStudent): Observable<IStudent> =>
    this.http.put(environment.khub_api_endpoint, student)
      .map(resp =>
        resp.json()
      );
}

所以,当我运行“awsSigV4Service(config).signRequest(request);”和console.log生成的标题输出并将它们放入Postman我得到的响应是:“我们计算的请求签名与您提供的签名不匹配。请检查您的AWS Secret Access Key和签名方法。有关详细信息,请参阅服务文档。 “。 同样,当我尝试使用Angular应用程序中我的服务的签名标头执行GET请求时,我会收到错误。

有人能指出我正确的方向吗?我在这里缺少什么吗?

0 个答案:

没有答案