aws lambda_API网关和AWS Lambda进行身份验证
aws lambda
當Foreach最初踏上微服務之路時,我們并沒有真正構建微服務。 我們以為我們做到了,但是我們所有的服務中總存在一些邏輯。 當然,每個服務實際上應該只專注于自己的任務,而不應該專注于屬于另一個微服務的事物。 我們這方面最明顯的棘手是認證和授權邏輯。
在某個時候,我們有幾個“微”服務,它們根據AuthenticationService (甚至在較早的日子,甚至是針對共享數據庫)驗證了傳入請求的Authorization標頭。 這給我們的AuthenticationService比我們想要的更多的負載(多次驗證同一個令牌),但是這也導致在所有這些服務中都存在一些重要的代碼。 而且,正如任何開發人員所知,共享代碼鋪平了通往地獄的道路。 微服務變得超出其實際用途,這使得它們變得更難開發和維護。
在尋求救贖的過程中,我們Swift確定了一些可以幫助我們的解決方案。
JSON Web令牌
我們考慮的第一件事是開始使用JSON Web令牌(JWT) 。 JWT是一種開放標準,它定義了一種獨立的方式來在各方之間安全地傳輸信息。 自包含意味著令牌本身可以包含我們需要的所有信息,例如用戶的標識符或用戶名。 安全意味著其他方不可能干擾這些令牌。 令牌包含一個加密部分,要解密它,您需要一個只有您知道的秘密密鑰。 換句話說,如果令牌已被篡改,您將知道。
JWT是一個非常有趣的領導者,因為從我們這方面進行最小的調整,我們從理論上甚至可以消除微服務中的一些額外工作量(它們無論如何都不應這樣做)。 令牌的驗證將是一個最小的過程,可以很好地集成到Spring框架中,因此我們不需要那么多代碼。 令牌還將包含我們需要的所有信息,因此我們不再需要從另一個Web服務請求此信息。
但是,JWT的問題在于,已經有其他各方開發的其他應用程序與API集成在一起。 事實證明,當我們開始分發JWT令牌時,并不是所有的應用程序都那么滿意。 由于短期內無法更改這些應用程序,因此我們暫時保留了這一想法。
API網關
我們的另一個想法是引入API網關。 這可以看作是我們API的包裝,意在為最終用戶抽象我們的API。 它可能會更改對另一種格式的響應。 它可以將多個HTTP請求合并為一個請求。 或者它可以提供其他監視功能(例如“誰向某個端點發送垃圾郵件?”)。 但最重要的是,它應該抽象出與身份驗證有關的所有內容。
在我們的例子中,想法是API網關甚至在請求被代理到我們的應用程序之前都會驗證傳入的Authorization標頭。 它應該緩存結果,以便如果同一用戶請求五個端點,我們仍然每小時僅驗證一次令牌,并且應該將身份驗證信息傳遞給我們的API,以便我們知道誰在請求資源。
我們的解決方案:AWS API Gateway
https://docs.aws.amazon.com/apigateway/latest/developerguide/images/custom-auth-workflow.png
市場上有許多符合此描述的產品,但是經過一番考慮,我們決定嘗試一下AWS API Gateway。 我們實施了自定義的“授權人”。 這是一個Lambda函數,它接收客戶端提供的授權令牌作為輸入,并返回客戶端是否有權訪問所請求的資源。 如果身份驗證被拒絕,API網關將向客戶端返回403 HTTP代碼。 否則,該請求將被代理到我們的服務中。 授權者Lambda的結果在緩存中保留了一個小時。 我們還希望使用HTTP標頭將用戶的身份傳遞給我們的基礎服務。 這樣,我們知道誰在我們的應用程序中執行請求。
授權者
我們的自定義Lambda函數是用Python編寫的。 它從傳入的請求中獲取Authorization標頭,并向我們的AuthenticationService啟動HTTP請求-這是我們唯一可以驗證傳入的信息是否有效以及令牌適用于誰的地方。 這個HTTP請求將告訴我們最終用戶是誰。
Lambda函數的代碼(主要基于AWS提供的示例代碼)如下所示:
from __future__ import print_function import re import urllib2 import base64 import json import os def lambda_handler(event, context): print("Client token (provided): " + event['authorizationToken']) clientAuthorizationToken = re.sub('^%s' % 'Bearer', '', re.sub('^%s' % 'bearer', '', event['authorizationToken'])).strip() print("Client token (parsed): " + clientAuthorizationToken) print("Method ARN: " + event['methodArn']) url = os.environ['CHECK_TOKEN_ENDPOINT'] + "?token=" + clientAuthorizationToken print("Check token URL: " + url) authorizationHeader = 'Basic %s' % base64.b64encode(os.environ['CHECK_TOKEN_ENDPOINT_CLIENT_ID'] + ':' + os.environ['CHECK_TOKEN_ENDPOINT_CLIENT_SECRET']) print("Our authorization header: " + authorizationHeader) tmp = event['methodArn'].split(':') apiGatewayArnTmp = tmp[5].split('/') awsAccountId = tmp[4] policy = AuthPolicy('urn:user:unknown', awsAccountId) policy.restApiId = apiGatewayArnTmp[0] policy.region = tmp[3] policy.stage = apiGatewayArnTmp[1] request = urllib2.Request(url, headers={"Authorization": authorizationHeader}) try: result = urllib2.urlopen(request) data = json.load(result) print("HTTP Response data: " + str(data)) context = { 'userUrn': data['user_urn'] if data.has_key('user_urn') else None, 'clientId': data['client_id'] } policy.principalId = data['user_urn'] if data.has_key('user_urn') else 'urn:client:%s' % data['client_id'] policy.allowMethod('*', '*') print('Allowing resource %s. Client: %s, User: %s, Principal: %s' % (policy.allowMethods[0]['resourceArn'], context['clientId'], context['userUrn'], policy.principalId)) except urllib2.HTTPError, e: print("Error during the HTTP call: %s" % e) policy.denyAllMethods() context = {} authResponse = policy.build() authResponse['context'] = context return authResponse class HttpVerb: GET = 'GET' POST = 'POST' PUT = 'PUT' PATCH = 'PATCH' HEAD = 'HEAD' DELETE = 'DELETE' OPTIONS = 'OPTIONS' ALL = '*' class AuthPolicy(object): awsAccountId = '' principalId = '' version = '2012-10-17' pathRegex = '^[/.a-zA-Z0-9-\*]+$' allowMethods = [] denyMethods = [] restApiId = '*' region = '*' stage = '*' def __init__(self, principal, awsAccountId): self.awsAccountId = awsAccountId self.principalId = principal self.allowMethods = [] self.denyMethods = [] def _addMethod(self, effect, verb, resource, conditions): if verb != '*' and not hasattr(HttpVerb, verb): raise NameError('Invalid HTTP verb ' + verb + '. Allowed verbs in HttpVerb class') resourcePattern = re.compile(self.pathRegex) if not resourcePattern.match(resource): raise NameError('Invalid resource path: ' + resource + '. Path should match ' + self.pathRegex) if resource[:1] == '/': resource = resource[1:] resourceArn = 'arn:aws:execute-api:{}:{}:{}/{}/{}/{}'.format(self.region, self.awsAccountId, self.restApiId, self.stage, verb, resource) if effect.lower() == 'allow': self.allowMethods.append({ 'resourceArn': resourceArn, 'conditions': conditions }) elif effect.lower() == 'deny': self.denyMethods.append({ 'resourceArn': resourceArn, 'conditions': conditions }) def _getEmptyStatement(self, effect): statement = { 'Action': 'execute-api:Invoke', 'Effect': effect[:1].upper() + effect[1:].lower(), 'Resource': [] } return statement def _getStatementForEffect(self, effect, methods): statements = [] if len(methods) > 0: statement = self._getEmptyStatement(effect) for curMethod in methods: if curMethod['conditions'] is None or len(curMethod['conditions']) == 0: statement['Resource'].append(curMethod['resourceArn']) else: conditionalStatement = self._getEmptyStatement(effect) conditionalStatement['Resource'].append(curMethod['resourceArn']) conditionalStatement['Condition'] = curMethod['conditions'] statements.append(conditionalStatement) if statement['Resource']: statements.append(statement) return statements def allowAllMethods(self): self._addMethod('Allow', HttpVerb.ALL, '*', []) def denyAllMethods(self): self._addMethod('Deny', HttpVerb.ALL, '*', []) def allowMethod(self, verb, resource): self._addMethod('Allow', verb, resource, []) def denyMethod(self, verb, resource): self._addMethod('Deny', verb, resource, []) def allowMethodWithConditions(self, verb, resource, conditions): self._addMethod('Allow', verb, resource, conditions) def denyMethodWithConditions(self, verb, resource, conditions): self._addMethod('Deny', verb, resource, conditions) def build(self): if ((self.allowMethods is None or len(self.allowMethods) == 0) and (self.denyMethods is None or len(self.denyMethods) == 0)): raise NameError('No statements defined for the policy') policy = { 'principalId': self.principalId, 'policyDocument': { 'Version': self.version, 'Statement': [] } } policy['policyDocument']['Statement'].extend(self._getStatementForEffect('Allow', self.allowMethods)) policy['policyDocument']['Statement'].extend(self._getStatementForEffect('Deny', self.denyMethods)) return policy網關配置
創建Lambda函數之后,該配置網關了。 您可以在AWS控制臺中或使用CloudFormation模板執行此操作。 我們不會詳細解釋如何配置API網關,因為這是AWS站點上記錄良好的任務 。 但是,我將解釋一些配置授權者的細節。
授權人
在“ API網關配置”部分中的左側,您會看到“授權者”選項。 您可以在那里選擇創建新的授權者。 當您單擊按鈕時,您將看到以下表格:
重要事項:
- Lambda函數:選擇您之前創建的授權者Lambda
- Lamba事件有效負載:令牌
- 令牌來源:授權(如果您的客戶端使用授權標頭發送令牌)
- 授權緩存:已啟用
資源資源
接下來,我們轉到您要保護的方法。 單擊左側的資源,然后在列表中選擇一種方法。 您應該看到類似于以下屏幕的屏幕:
點擊“方法請求”。 然后,您可以在頂部配置為使用之前添加的授權者。
返回上一個屏幕,然后單擊“集成請求”。 在底部,我們將配置一些要發送到API的標頭。 這些包含有關用戶的信息,我們將在API中使用這些信息來了解誰在發出請求。 注意:我們不必擔心惡意用戶在請求中發送這些標頭。 它們將被我們的自定義授權者覆蓋。
未來
雖然我們當前的實施在生產中運行良好,但我們始終在尋找有關如何改進我們的產品以及由此向客戶提供服務的想法。 我們將繼續關注的事情之一是,有一天開始使用JWT令牌,這很可能與API Gateway結合使用。 這將使設置更加容易,但是將需要對某些應用程序進行更改,這是我們目前無法做到的。
此外,我們確實對如何從API網關中獲取更多信息有一些想法。 我們對每個應用程序和每個用戶的速率限制非常感興趣。 我們希望能夠以這種方式配置移動應用程序,例如,僅允許每小時執行一百個請求,或者僅允許某個最終用戶少量請求。
將API Gateway與AWS Lambda結合使用是一種相對簡單的方法,可以在您的應用程序中添加可靠的身份驗證方法,而不會造成其他服務中斷。
翻譯自: https://www.javacodegeeks.com/2018/11/api-gateway-aws-lambda-authentication.html
aws lambda
總結
以上是生活随笔為你收集整理的aws lambda_API网关和AWS Lambda进行身份验证的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 售货机备案依据(售货机备案)
- 下一篇: maven 文件上传下载_使用Maven