You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Identity Framework实现ASP.NET Core Web API多方式登录授权

基于ASP.NET Core Identity规范实现多方式登录流程

一、基础配置:集成Identity与JWT认证

先确保项目正确集成ASP.NET Core Identity并配置JWT认证:

  1. 注册Identity服务
    在Program.cs中添加以下配置:
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// 注册Identity,可使用自定义User模型扩展字段
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

// 配置JWT认证
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(builder.Configuration["Jwt:Key"]))
    };
});

builder.Services.AddAuthorization();
  1. 启用认证授权中间件
    在Program.cs的管道中添加:
app.UseAuthentication();
app.UseAuthorization();

二、用UserManager处理第三方登录用户的查找与创建

第三方登录核心是通过第三方唯一标识查找或创建本地用户,用UserManager实现更规范:

1. 定义统一第三方用户模型

适配Google、Facebook、Apple的用户信息格式:

public class ExternalUserInfo
{
    public string Provider { get; set; } // 标识第三方平台:"Google"/"Facebook"/"Apple"
    public string ProviderUserId { get; set; } // 第三方平台的唯一用户ID
    public string Email { get; set; }
    public string UserName { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}

2. 封装查找/创建用户逻辑

private async Task<ApplicationUser> FindOrCreateExternalUserAsync(ExternalUserInfo externalUser)
{
    // 先通过第三方平台标识和用户ID查找本地用户
    var existingUser = await _userManager.FindByLoginAsync(externalUser.Provider, externalUser.ProviderUserId);
    if (existingUser != null)
        return existingUser;

    // 未找到则通过邮箱匹配已有用户
    existingUser = await _userManager.FindByEmailAsync(externalUser.Email);
    if (existingUser != null)
    {
        // 将第三方登录关联到已有用户
        await _userManager.AddLoginAsync(existingUser, new UserLoginInfo(
            externalUser.Provider, 
            externalUser.ProviderUserId, 
            externalUser.Provider));
        return existingUser;
    }

    // 创建新用户
    var newUser = new ApplicationUser
    {
        UserName = externalUser.UserName,
        Email = externalUser.Email,
        EmailConfirmed = true // 第三方登录默认邮箱已验证
    };

    var createResult = await _userManager.CreateAsync(newUser);
    if (!createResult.Succeeded)
        throw new InvalidOperationException($"创建用户失败:{string.Join(", ", createResult.Errors.Select(e => e.Description))}");

    // 添加第三方登录关联
    await _userManager.AddLoginAsync(newUser, new UserLoginInfo(
        externalUser.Provider, 
        externalUser.ProviderUserId, 
        externalUser.Provider));

    // 初始化用户角色、积分等业务逻辑
    await _userManager.AddToRoleAsync(newUser, "User");

    return newUser;
}

三、用SignInManager生成规范JWT令牌

SignInManager的CreateUserPrincipalAsync方法可自动生成包含用户身份、角色的Claims,替代手动拼接逻辑:

1. 封装Token生成服务

public class TokenService
{
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IConfiguration _configuration;
    private readonly UserManager<ApplicationUser> _userManager;

    public TokenService(SignInManager<ApplicationUser> signInManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
    {
        _signInManager = signInManager;
        _configuration = configuration;
        _userManager = userManager;
    }

    public async Task<AccessTokenResponse> GenerateAccessTokenAsync(ApplicationUser user)
    {
        // 生成包含用户所有身份信息的ClaimsPrincipal
        var principal = await _signInManager.CreateUserPrincipalAsync(user);
        
        // 添加自定义业务Claims(如用户ID、积分等)
        var customClaims = new List<Claim>
        {
            new Claim("UserId", user.Id),
            // new Claim("Points", user.Points.ToString()) // 示例:若用户模型有积分字段
        };

        var claims = principal.Claims.Union(customClaims);

        var key = new SymmetricSecurityKey(Convert.FromBase64String(_configuration["Jwt:Key"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var expiration = DateTime.UtcNow.AddHours(int.Parse(_configuration["Jwt:ExpirationInHours"]!));

        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: expiration,
            signingCredentials: creds);

        var jwt = new JwtSecurityTokenHandler().WriteToken(token);
        var refreshToken = await GenerateRefreshTokenAsync(user);

        return new AccessTokenResponse
        {
            AccessToken = jwt,
            ExpiresIn = (int)(expiration - DateTime.UtcNow).TotalSeconds,
            RefreshToken = refreshToken.Token
        };
    }

    private async Task<RefreshToken> GenerateRefreshTokenAsync(ApplicationUser user)
    {
        // 生成并存储刷新令牌(需自行实现RefreshToken实体和存储逻辑)
        var refreshToken = new RefreshToken
        {
            Token = Guid.NewGuid().ToString(),
            UserId = user.Id,
            Expires = DateTime.UtcNow.AddDays(7)
        };

        // _context.RefreshTokens.Add(refreshToken);
        // await _context.SaveChangesAsync();

        return refreshToken;
    }
}

2. 注册Token服务

在Program.cs中添加:

builder.Services.AddScoped<TokenService>();

四、重构第三方登录控制器(以Google为例)

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IGoogleAuthService _googleAuthService;
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly TokenService _tokenService;

    public AuthController(IGoogleAuthService googleAuthService, UserManager<ApplicationUser> userManager, TokenService tokenService)
    {
        _googleAuthService = googleAuthService;
        _userManager = userManager;
        _tokenService = tokenService;
    }

    [HttpPost("google-login")]
    public async Task<IActionResult> GoogleLogin([FromBody] GoogleLoginRequest request)
    {
        try
        {
            var googleUser = await _googleAuthService.GetUserDetails(request.IdToken);
            if (googleUser == null)
                return BadRequest("Google服务返回无效用户信息");

            var externalUser = new ExternalUserInfo
            {
                Provider = "Google",
                ProviderUserId = googleUser.Sub,
                Email = googleUser.Email,
                UserName = googleUser.Email.Split('@')[0],
                FirstName = googleUser.GivenName,
                LastName = googleUser.FamilyName
            };

            var user = await FindOrCreateExternalUserAsync(externalUser);
            var tokenResponse = await _tokenService.GenerateAccessTokenAsync(user);

            return Ok(tokenResponse);
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
        catch (Exception)
        {
            return StatusCode(StatusCodes.Status500InternalServerError, "服务器内部错误");
        }
    }

    private async Task<ApplicationUser> FindOrCreateExternalUserAsync(ExternalUserInfo externalUser)
    {
        var existingUser = await _userManager.FindByLoginAsync(externalUser.Provider, externalUser.ProviderUserId);
        if (existingUser != null)
            return existingUser;

        existingUser = await _userManager.FindByEmailAsync(externalUser.Email);
        if (existingUser != null)
        {
            await _userManager.AddLoginAsync(existingUser, new UserLoginInfo(
                externalUser.Provider, 
                externalUser.ProviderUserId, 
                externalUser.Provider));
            return existingUser;
        }

        var newUser = new ApplicationUser
        {
            UserName = externalUser.UserName,
            Email = externalUser.Email,
            EmailConfirmed = true
        };

        var createResult = await _userManager.CreateAsync(newUser);
        if (!createResult.Succeeded)
            throw new InvalidOperationException($"创建用户失败:{string.Join(", ", createResult.Errors.Select(e => e.Description))}");

        await _userManager.AddLoginAsync(newUser, new UserLoginInfo(
            externalUser.Provider, 
            externalUser.ProviderUserId, 
            externalUser.Provider));
        await _userManager.AddToRoleAsync(newUser, "User");

        return newUser;
    }
}

五、账号密码登录的规范实现

[HttpPost("password-login")]
public async Task<IActionResult> PasswordLogin([FromBody] PasswordLoginRequest request)
{
    var user = await _userManager.FindByEmailAsync(request.Email);
    if (user == null)
        return BadRequest("邮箱或密码错误");

    var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure: false);
    if (!result.Succeeded)
        return BadRequest("邮箱或密码错误");

    var tokenResponse = await _tokenService.GenerateAccessTokenAsync(user);
    return Ok(tokenResponse);
}

六、核心优势

  • 复用Identity内置方法,避免手动操作数据库表,降低出错风险
  • 自动生成符合规范的Claims集合,无需手动拼接身份、角色信息
  • 统一第三方登录核心逻辑,新增平台只需替换令牌验证部分,业务流程复用

内容的提问来源于stack exchange,提问作者yeyeyerman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 21:37:05