.net 8 C# 集成 AWS Cognito SMS/Email 注册与登录
本文主要分為三個(gè)部分:
1、描述 cognito 涉及的專業(yè)術(shù)語 以及 交互流程
2、.net 集成的代碼
3、感想
* 閱讀提示 :鼠標(biāo)懸停在 章節(jié)標(biāo)題 上可見 文章目錄
1. Cognito 概念
1.1 關(guān)鍵詞
進(jìn)入 Amazon Cognito,會(huì)先看到 user pool 的列表
|
cognito
|
亞馬遜(Amazon)云 提供的一種用戶管理服務(wù),簡(jiǎn)化用戶注冊(cè)、登錄和授權(quán)、鑒權(quán)相關(guān)的服務(wù)操作。
可以在 cognito 上創(chuàng)建用戶池,管理用戶注冊(cè)、登錄和鑒權(quán)相關(guān)的問題;
可以創(chuàng)建 identity pool 對(duì)用戶進(jìn)行授權(quán)相關(guān)的約束;
支持多種身份驗(yàn)證方式,例如 用戶 / 密碼 登錄,社交帳號(hào)( Facebook,Google 等)登錄,企業(yè)身份供應(yīng)商
* What is Amazon Cognito? - Amazon Cognito
|
|
user pool
|
用戶池,多租戶 / 不同的服務(wù)供應(yīng)商,擁有自己的用戶群體;
用戶池支持用戶注冊(cè)、登錄、鑒權(quán)、賬號(hào)恢復(fù)等。
|
|
identity pool
|
資格授權(quán)池,可以提供一些 AWS 的資格認(rèn)證到通過驗(yàn)證的用戶上
|
|
app client
|
用戶池所關(guān)聯(lián)的應(yīng)用服務(wù)端,必須要配置到相關(guān)的用戶池上才允許訪問該用戶池
|
選定一個(gè)用戶池,可以查看該用戶池的信息,左側(cè)欄見其 功能 / 配置
user pool 功能 / 配置 目錄列表
* 為了降低描述的復(fù)雜度,此處僅做最小配置展開
* 加粗為需要重點(diǎn)關(guān)注;此處不展開 IdentityPool 以及 userPool.Security 和 userPool.Branding
|
Seconcd |
Third |
Description |
Remark |
|
|
User Pool |
Overview |
用戶池的基本信息 |
集成時(shí)需要找到這個(gè)用戶池的 ID、ARN 等信息 |
|
|
Applications |
app client |
配置可訪問該用戶池的應(yīng)用端信息 |
在此處可以限制一些讀寫的權(quán)限 |
|
|
User Management |
Users |
用戶池里面的每個(gè)用戶 |
相當(dāng)于用戶表,可以自定義用戶屬性 custom user attribute |
|
|
Groups |
用戶分組,主要用于指定某個(gè)組內(nèi)的用戶可以享有某些權(quán)限 |
按需求來,可選配置 |
||
|
Authentication |
Authentication methods |
用戶驗(yàn)證方式的配置 |
可以在此處配置密碼驗(yàn)證 或 通過短信驗(yàn)證 或 通過郵箱驗(yàn)證 |
|
|
Sign-in |
用戶登錄時(shí)的相關(guān)配置 |
例如密碼復(fù)雜度,登錄方式 |
||
|
Sign-up |
用戶注冊(cè)時(shí)的相關(guān)配置 |
|||
|
Social and external providers |
通過第三方登錄的配置,像是通過 Facebook, Google, Amazon or Apple..等其他供應(yīng)商來授權(quán) |
|||
|
Extensions |
通過一些自定義的驗(yàn)證行為,觸發(fā) AWS lambda function。此處是觸發(fā)器的配置。 |
此處的觸發(fā)條件是 cognito 規(guī)定好的。也即僅可以配置:要不要觸發(fā),觸發(fā)哪一個(gè)function |
||
|
Security |
AWS WAF |
|||
|
Threat protection |
||||
|
Log streaming |
||||
|
Branding |
Domain |
|||
|
Managed login |
||||
|
Message templates |
||||
|
Identity Pool |
Overview |
|||
|
Authentication Providers |
||||
|
Roles |
||||
|
IAM Policies |
||||
|
Data Synchronization |
Cognito 主要是做 用戶管理 的事情,它支持通過 密碼、手機(jī)短信、郵箱地址、其他企業(yè)供應(yīng)商(例如 facebook、apple account 等) 等方式授權(quán) token,
本文主要描述 自定義校驗(yàn) custom authentication 的方式,此處要求在 user pool 中配置 3 個(gè) AWS lambda function ,cognito 將會(huì)觸發(fā)它的執(zhí)行。
Lambda function 也是 Amazon web service 其中之一,此處可以簡(jiǎn)單地把其當(dāng)作一個(gè)AWS的 api 方法。
這里涉及到 3 個(gè)觸發(fā)器:define auth challenge,create auth challenge,verify auth challenge
|
Custom authentication trigger type |
Define Auth Challenge |
custom authentication 的第一步,這里會(huì)返回 custom challenge name 到 cognito;同時(shí)也可以基于用戶邏輯,直接在此處發(fā)布 token 。 |
|
Create Auth Challenge |
custom authentication 流程的第二步,實(shí)現(xiàn)自定義身份驗(yàn)證的步驟。通常在這一步中寫入驗(yàn)證的答案,驗(yàn)證的問題可以是 captchas 或者其他安全問題 或 驗(yàn)證碼 等。 |
|
|
Verify Auth Challenge |
custom authentication 的第三步,校驗(yàn)用戶輸入的答案與在 create auth challenge 中預(yù)設(shè)的答案是否一致 |
下文將描述我們的代碼會(huì)如何與 cognito 交互,又是如何觸發(fā)對(duì)應(yīng)的 lambda function。
1.2. 流程圖
登錄步驟 1:輸入賬號(hào)
- 前端第一次調(diào)用接口,是提交登錄的請(qǐng)求;
- 后端調(diào)用 AWSSDK.CognitoIdentifyProvider 中 cognitoClient.InitiateAuthAsync 方法,這個(gè)方法是 cognito 支持的自定義驗(yàn)證方式,它會(huì)自動(dòng)觸發(fā)到
define auth challenge,create auth challenge; - 此處發(fā)送短信的方式是后端去調(diào)用了一個(gè) AWS Lambda Function
登錄步驟 2:輸入驗(yàn)證碼
- 前端第二次調(diào)用的接口,是提交登錄的驗(yàn)證碼
- 后端會(huì)把驗(yàn)證碼發(fā)送給 user pool 去做校驗(yàn),通過了校驗(yàn)才能成功授權(quán)返回 token
* AWS Lambda Triggers
|
場(chǎng)景 |
SDK Function |
Trigger the Lambda |
|
sign-in 登錄 |
InitiateAuthAsync |
Define Auth Challenge, Create Auth Challenge |
|
RespondToAuthChallengeAsync |
Verify Auth Challenge |
|
|
sign-up 注冊(cè) |
SignUpAsync |
Custom message trigger |
|
ConfirmSignUpAsync |
Define auth challenge
- 這個(gè) trigger 用于決定下一個(gè)驗(yàn)證步驟,可以在這里配置走向密碼驗(yàn)證或者自定義的驗(yàn)證方式。
- 基于正確的 user session 上,它進(jìn)一步定義驗(yàn)證的流程
例如,用戶通過密碼驗(yàn)證之后,將進(jìn)入到手機(jī)短信驗(yàn)證
// cognito 將會(huì)觸發(fā)到配置在 user pool 上的 lambda trigger,event 是它的入?yún)?br />
exports.handler = async (event) => {
if (event.request.session.length === 1 && event.request.session[0].challengeName === 'SRP_A') {
// 是否直接發(fā)布 token
event.response.issueTokens = false;
// 是否驗(yàn)證失敗
event.response.failAuthentication = false;
// 下一個(gè)驗(yàn)證方式的名字
event.response.challengeName = 'PASSWORD_VERIFIER';
} else if (event.request.session.length === 2 && event.request.session[1].challengeName === 'PASSWORD_VERIFIER' && event.request.session[1].challengeResult === true) {
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
} else if (event.request.session.length === 3 && event.request.session[2].challengeName === 'CUSTOM_CHALLENGE' && event.request.session[2].challengeResult === true) {
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
event.response.issueTokens = false;
event.response.failAuthentication = true;
}
return event;
};
Create auth challenge
- 一旦
Define Auth Challenge觸發(fā)器指明使用自定義驗(yàn)證,那么Create Auth Challenge就會(huì)被觸發(fā)去生成驗(yàn)證的內(nèi)容。 - 在這個(gè)步驟中所創(chuàng)建的校驗(yàn)內(nèi)容,是用戶必須要答復(fù)的。例如 發(fā)到 SMS、Email 上的驗(yàn)證碼,或者 CAPTCHA 之類的驗(yàn)證問題。
例如在 Create Auth Challenge 上設(shè)定一個(gè)將發(fā)送到 Email 的驗(yàn)證碼(one-time password,簡(jiǎn)稱 OTP),
此時(shí) Amazon Cognito 其實(shí)會(huì)存儲(chǔ)這個(gè) event 內(nèi)容,關(guān)聯(lián)到用戶的 session 和 用于 Verify Auth Challenge 步驟作為校驗(yàn)的答案(這里是驗(yàn)證碼)
lambda function 實(shí)現(xiàn)示例:
exports.handler = async (event) => {
if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
const otp = '654321'; // Generate or set your OTP here
event.response.publicChallengeParameters = { otp: 'Enter the OTP sent to your email' };
event.response.privateChallengeParameters = { otp: otp };
event.response.challengeMetadata = 'CUSTOM_CHALLENGE';
}
return event;
};
Verify auth challenge
- 該觸發(fā)器在用戶提交驗(yàn)證碼之后被觸發(fā)
- 該觸發(fā)器接收到用戶輸入的 驗(yàn)證碼 后會(huì)與
Create Auth Challenge中的驗(yàn)證碼相互匹配。如果校驗(yàn)正確,cognito 會(huì)處理返回的 event 并生成 token
lambda function 實(shí)現(xiàn)示例:
// TriggerSource is VerifyAuthChallengeResponse_Authentication
exports.handler = async (event) => {
const expectedOtp = event.request.privateChallengeParameters.otp;
const userResponse = event.request.challengeAnswer;
event.response.answerCorrect = userResponse === expectedOtp;
return event;
};
2. 集成到 .net 8 api
.NET 中需要使用 AWS SDK 提供的 API 來與 Cognito 進(jìn)行交互,
引用包:AWSSDK.CognitoIdentityProvider ,下文代碼使用的版本是 3.7.1.85
<PackageReference Include="AWSSDK.CognitoIdentityProvider" Version="3.7.1.85" />
添加引用
using Amazon.CognitoIdentityProvider;
using Amazon.CognitoIdentityProvider.Model;
cognitoClient 初始化:
var AWSregion = "ap-xxxxx-x";
_cognitoClient = new AmazonCognitoIdentityProviderClient(Amazon.RegionEndpoint.GetBySystemName(AWSregion));
2.1.1 注冊(cè) [signup]
public async Task<SignUpResponse> SignupAsync(SignUpDto user)
{
var userName = string.IsNullOrEmpty(user.Email) ? user.PhoneNumber : user.Email;
var request = new SignUpRequest
{
// 在 cognito user pool 中配置允許訪問該 user pool 的 client 后,可以在cognito 上查看到這個(gè) clientId
ClientId = _clientId,
// cognito 會(huì)校驗(yàn)代碼復(fù)雜度,需要在 user pool 中配置;但此示例中密碼是沒有作用的
Password = "carcar@2024",
// 此處可以填手機(jī)號(hào)或郵箱地址; cognito user pool 中顯示的 userName 是 user pool 用戶管理意義上的 uuid
Username = userName
};
// 自定義的 user attribute
var nameAttribute = new AttributeType
{
Name = "name",
Value = user.Name
};
request.UserAttributes.Add(nameAttribute);
// 自定義的 user attribute
var emailAttribute = new AttributeType
{
Name = "email",
Value = user.Email
};
request.UserAttributes.Add(emailAttribute);
return await _cognitoClient.SignUpAsync(request);
}
可配置密碼復(fù)雜度配置
可以配置對(duì) user attribute 的必填要求
查看 user attribute
2.1.2 注冊(cè)驗(yàn)證 [signup-confirm]
var confirmSignUpRequest = new ConfirmSignUpRequest
{
ClientId = "client id",
Username = "user phone / email",
ConfirmationCode = "verification code from sms or email"
};
var confirmSignUpResponse = await cognitoClient.ConfirmSignUpAsync(confirmSignUpRequest);
Console.WriteLine($"User {confirmSignUpResponse.UserConfirmed} confirmed successfully.")
2.1.3 重新發(fā)送驗(yàn)證碼 [resend-confirmation]
// userName 可以是 cognito UserName,又或者是注冊(cè)時(shí)寫入的 userName 即郵箱或手機(jī)號(hào)
public async Task ResendConfirmationAsync(string userName)
{
var request = new ResendConfirmationCodeRequest
{
ClientId = _clientId,
Username = userName
};
var response = await _cognitoClient.ResendConfirmationCodeAsync(request);
}
2.2.1 登錄 [signin]
var authRequest = new InitiateAuthRequest
{
ClientId = "the client id which configures in the user pool",
AuthFlow = AuthFlowType.CUSTOM_AUTH,
AuthParameters = new Dictionary<string, string>
{
{ "USERNAME", "your_username_or_phone_or_email" }
}
};
var authResponse = await cognitoClient.InitiateAuthAsync(authRequest);
2.2.2 登錄驗(yàn)證 [signin-confirm]
校驗(yàn)驗(yàn)證碼時(shí),調(diào)用 RespondToAuthChallengeAsync
var respondToAuthChallengeRequest = new RespondToAuthChallengeRequest
{
ChallengeName = authResponse.ChallengeName,
ClientId = "your_client_id",
ChallengeResponses = new Dictionary<string, string>
{
{ "USERNAME", "your_username_or_phone_or_email" },
{ "SMS_MFA_CODE", "user_received_code" }
},
Session = authResponse.Session
};
var challengeResponse = await cognitoClient.RespondToAuthChallengeAsync(respondToAuthChallengeRequest);
var idToken = challengeResponse.AuthenticationResult.IdToken;
2.3 token 驗(yàn)證
在 Program 或 StartUp 的配置文件中,需要配置 Authentication 中間件
* token 參數(shù)說明 - 點(diǎn)此跳轉(zhuǎn)查看
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = $"https://cognito-idp.{AWSregion}.amazonaws.com/{userPoolId}",
// ValidAudience = {userPoolId},
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// Get JsonWebKeySet from AWS
var json = new WebClient().DownloadString($"https://cognito-idp.{AWSregion}.amazonaws.com/{userPoolId}/.well-known/jwks.json");
// Deserialize the result
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
// Cast the result to be the type expected by IssuerSigningKeyResolver
return (IEnumerable<SecurityKey>)keys;
}
};
});
3. 為什么寫這一篇文章
很多開發(fā)可能和我一樣,沒有成為一名云上工程師,或者說項(xiàng)目里并沒有使用 Amazon Web Service,那么對(duì)于如何集成 AWS 可能是不感興趣的。
那我為什么寫這篇文章?
1、國(guó)內(nèi)對(duì) AWS 的應(yīng)用較少,AWS 相關(guān)的資料大多是英文的,其實(shí)解讀下來真的挺花時(shí)間。
2、現(xiàn)在新穎的技術(shù)層出不窮,我的希望是在探索 cognito 的過程中,建立一個(gè)快速理解的方法論。
這背后考驗(yàn)的是專業(yè)知識(shí)以及邏輯梳理能力。我們一定是:知其所以然,才能應(yīng)對(duì)多變的表象。
當(dāng)然,這里我指的是對(duì)云服務(wù)如何集成傳統(tǒng)應(yīng)用,或者說“我的應(yīng)用要怎么上云”。
3、借此機(jī)會(huì)看一下云服務(wù)的設(shè)計(jì),現(xiàn)在都怎么玩的。
理解云服務(wù)的應(yīng)用不單止為我們多提供一種解決方案,在排查集成云集成中產(chǎn)生的問題,也會(huì)有所啟發(fā)。
是否能夠以小見大,找到表象的本質(zhì)?
在既定的解決方案架構(gòu)里面找到可以拓展的共性,能不能經(jīng)驗(yàn)遷移?
無論如何,希望這篇文章對(duì)你有所收益。
References
[1] Amazon Cognito Identity Provider examples using AWS SDK for .NET
[2] Authentication flow examples with .NET for Amazon Cognito
[3] Authenticating users in ASP.NET Core MVC using Amazon Cognito
[4] Securing ASP.NET Core API with JWT Token using AWS Cognito
總結(jié)
以上是生活随笔為你收集整理的.net 8 C# 集成 AWS Cognito SMS/Email 注册与登录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Could not find desti
- 下一篇: python pyqt面板切换