请问如何拓展MapIdentityApi<TUser>(IEndpointRouteBuilder)的"/refresh" and "/login" endpoint?

Yishen Yang 45 信誉分
2024-10-11T02:34:55.5366667+00:00

您好,

我正在学习用户身份的验证和授权。

我正在开发一个简单的Web API程序来学习。

我了解到MapIdentityApi这个方法可以很方便的提供很多默认的endpoint。

https://video2.skills-academy.com/en-us/dotnet/api/microsoft.aspnetcore.identity.data?view=aspnetcore-8.0

我想了解如何扩展其中的login和refresh端点。

login端点在登录后会返回Access Token和Refresh Token。

但Refresh Token没有被设置为HttpOnly Cookie(是否有必要设置成HttpOnly Cookie?)。

同样的,refresh端点接收前端Post请求里携带的Refresh Token,而不是从Cookie获取。

谢谢。

ASP.NET Core
ASP.NET Core
.NET Framework 中一套用于生成 Web 应用程序和 XML Web 服务的技术。
31 个问题
C#
C#
一种面向对象的类型安全的编程语言,它起源于 C 语言系列,包括对面向组件的编程的支持。
184 个问题
0 个注释 无注释
{count} 票

1 个答案

排序依据: 非常有帮助
  1. JasonPan - MSFT 5,796 信誉分 Microsoft 供应商
    2024-10-14T08:21:45.69+00:00

    Hi @Yishen Yang,

    对于您的问题可以有两种方式来实现,一个是添加自定义的http请求接口,另一个是扩展IdentityApiExtensions

    我这里简单的对IdentityApiExtensions进行了扩展,仅供您学习与参考,如果要应用正式环境的站点的话,建议进行安全评估与测试。

    using Microsoft.AspNetCore.Identity;
    using Microsoft.IdentityModel.Tokens;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    namespace IdentityApiDemo
    {
        public static class IdentityApiExtensions
        {
            public static IEndpointRouteBuilder MapCustomIdentityApi(this IEndpointRouteBuilder endpoints)
            {
                endpoints.MapPost("/login", async context =>
                {
                    var userManager = context.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
                    var signInManager = context.RequestServices.GetRequiredService<SignInManager<IdentityUser>>();
                    var jwtKey = "YourJwtSecretKey"; // 请使用安全的密钥
                    var jwtIssuer = "YourIssuer";    // 可选
                    // 从请求中获取用户名和密码
                    var form = await context.Request.ReadFormAsync();
                    var username = form["username"];
                    var password = form["password"];
                    var user = await userManager.FindByNameAsync(username);
                    if (user != null && await userManager.CheckPasswordAsync(user, password))
                    {
                        var accessToken = GenerateAccessToken(user, jwtKey, jwtIssuer);
                        var refreshToken = GenerateRefreshToken();
                        context.Response.Cookies.Append("RefreshToken", refreshToken, new CookieOptions
                        {
                            HttpOnly = true,
                            Secure = true,
                            SameSite = SameSiteMode.Strict,
                            Expires = System.DateTimeOffset.UtcNow.AddDays(7)
                        });
                        await context.Response.WriteAsJsonAsync(new { AccessToken = accessToken });
                        return;
                    }
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                });
                // 添加自定义的刷新端点
                endpoints.MapPost("/refresh", async context =>
                {
                    var userManager = context.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
                    if (context.Request.Cookies.TryGetValue("RefreshToken", out var refreshToken))
                    {
                        var userId = GetUserIdFromRefreshToken(refreshToken);
                        if (userId != null)
                        {
                            var user = await userManager.FindByIdAsync(userId);
                            if (user != null)
                            {
                                var jwtKey = "YourJwtSecretKey"; // 请使用安全的密钥
                                var jwtIssuer = "YourIssuer";    // 可选
                                var newAccessToken = GenerateAccessToken(user, jwtKey, jwtIssuer);
                                var newRefreshToken = GenerateRefreshToken();
                                context.Response.Cookies.Append("RefreshToken", newRefreshToken, new CookieOptions
                                {
                                    HttpOnly = true,
                                    Secure = true,
                                    SameSite = SameSiteMode.Strict,
                                    Expires = System.DateTimeOffset.UtcNow.AddDays(7)
                                });
                                await context.Response.WriteAsJsonAsync(new { AccessToken = newAccessToken });
                                return;
                            }
                        }
                    }
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                });
                return endpoints;
            }
            private static string GenerateAccessToken(IdentityUser user, string jwtKey, string jwtIssuer)
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.UTF8.GetBytes(jwtKey);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new Claim[]
                    {
                    new Claim(ClaimTypes.NameIdentifier, user.Id),
                    new Claim(ClaimTypes.Name, user.UserName)
                    }),
                    Expires = System.DateTime.UtcNow.AddMinutes(30),
                    Issuer = jwtIssuer,
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
                };
                var token = tokenHandler.CreateToken(tokenDescriptor);
                return tokenHandler.WriteToken(token);
            }
            private static string GenerateRefreshToken()
            {
                return System.Guid.NewGuid().ToString();
            }
            private static string GetUserIdFromRefreshToken(string refreshToken)
            {
                return "testuser-id"; // 请替换为实际的用户 ID
            }
        }
    }
    

    注册(中间件顺序很重要):

    app.UseAuthorization();
    app.MapCustomIdentityApi();
    app.MapControllers();
    
    ...
    

    关于扩展方法里面细节,需要您自己去实现,扩展完成后,可以在写代码的时候,直接调用自定义的扩展方法。

    Best Regards

    Jason

    1 个人认为此答案很有帮助。
    0 个注释 无注释

你的答案

问题作者可以将答案标记为“接受的答案”,这有助于用户了解已解决作者问题的答案。