Net8+IdentityServer4+MySQL+Grant扩展(不同场景下的认证需求)

  • 基于IdentityServer4的统一身份认证中心
  • IdentityServer4是什么?能做什么?请参阅官方文档:https://identityserver4docs.readthedocs.io/zh-cn/latest/index.html
  • 知识储备
    • OAuth2.0
    • Client:客户端
    • Resource Owner:资源所有者
    • Authorization Server:认证服务器,即服务提供商专门用来处理认证的服务器。
    • Resource Server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
    • 4种授权模式:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner assword credentials)、客户端模式(client credentials)
    • OIDC
    • OpenId Connect = OIDC = Authentication + Authorization + OAuth2.0
    • OIDC是一个基于OAuth2协议的身份认证标准协议,解决的问题是用户认证,而不关心授权。
    •  ID Token
    •  OIDC对OAuth2最主要的扩展就是提供了ID Token.
    •  ID Token是一个安全令牌,是一个授权服务器提供的包含用户信息(由一组Cliams构成以及其他辅助的Cliams)的JWT格式的数据结构。
    •  ID Token的主要构成部分如下(使用OAuth2流程的OIDC)。
    iss = Issuer Identifier:必须。提供认证信息者的唯一标识。一般是一个https的url(不包含querystring和fragment部分)。

    sub = Subject Identifier:必须。iss提供的EU的标识,在iss范围内唯一。它会被RP用来标识唯一的用户。最长为255个ASCII个字符。

    aud = Audience(s):必须。标识ID Token的受众。必须包含OAuth2的client_id。

    exp = Expiration time:必须。过期时间,超过此时间的ID Token会作废不再被验证通过。

    iat = Issued At Time:必须。JWT的构建的时间。

    auth_time = AuthenticationTime:EU完成认证的时间。如果RP发送AuthN请求的时候携带max_age的参数,则此Claim是必须的。

    nonce:RP发送请求的时候提供的随机字符串,用来减缓重放攻击,也可以来关联ID Token和RP本身的Session信息。

    acr = Authentication Context Class Reference:可选。表示一个认证上下文引用值,可以用来标识认证上下文类。

    amr = Authentication Methods References:可选。表示一组认证方法。

    azp = Authorized party:可选。结合aud使用。只有在被认证的一方和受众(aud)不一致时才使用此值,一般情况下很少使用。
  • OAuth 2.0、OpenID、OpenID Connect三者的关系 ?
  • User:用户
  • IdentityServer4.EntityFrameworknuget包实现了所需的存储和服务,主要使用以下两个DbContexts:
  • ConfigurationDbContext - 作用于注册数据,如客户端,资源,scope等等
  • PersistedGrantDbContext - 作用于临时操作数据,如授权码,refresh tokens
  • 数据迁移
  • 下面所有操作都在Web项目中,配置好数据库连接,可先建库,也可不建库;
  • 首先,你需要在你的项目中安装以下NuGet包:
dotnet add package Pomelo.EntityFrameworkCore.MySql

dotnet add package IdentityServer4.EntityFramework
  • 然后,你需要在你的Startup.cs文件中配置IdentityServer4以使用MySQL和EF Core。你需要在AddIdentityServer方法中添加AddConfigurationStore和AddOperationalStore,并在其中配置MySQL数据库的连接字符串和版本:
public void ConfigureServices(IServiceCollection services)
{
    string connectionString = Configuration.GetConnectionString("DefaultConnection");
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    services.AddIdentityServer()
        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseMySql(connectionString, 
                    new MySqlServerVersion(new Version(8, 0, 21)), 
                    sql => sql.MigrationsAssembly(migrationsAssembly));
        })
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseMySql(connectionString, 
                    new MySqlServerVersion(new Version(8, 0, 21)), 
                    sql => sql.MigrationsAssembly(migrationsAssembly));
        });
}
  • 接下来,你需要创建迁移。在命令行中运行以下命令:
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb

dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
  • 最后,你需要更新数据库以应用这些迁移。在命令行中运行以下命令:
dotnet ef database update -c ConfigurationDbContext

dotnet ef database update -c PersistedGrantDbContext
  • 初始化数据
using (var scope = app.Services.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
    scope.ServiceProvider.GetService<PersistedGrantDbContext>().Database.Migrate();
    var context = scope.ServiceProvider.GetService<ConfigurationDbContext>();
    context.Database.Migrate();
}
  • 最后
  • 很多时候,统一身份认证中心需要同时满足不同场景下的身份认证需求,如:SaaS系统中常见的场景有:管理端登录、C端用户登录、B端用户登录,而且登录方式多样:微信授权、手机验证码,重要的是这些端的用户通常是不是一个表,甚至不在一个库,如何实现?
  • 我们的解决方案:
  • 扩展IDS4的Grant,在不同的自定义Grant中注入对应业务微服务的远程调用API(rpc&http),如下:
  • namespace Extensions.SMS
    {
        /// <summary>
        /// 自定义一个授权类型 AdminSMSGrantValidator 实现后端管理员用手机+短信验证码登录
        /// </summary>
        public class AdminSMSGrantValidator : IExtensionGrantValidator
        {
            public string GrantType => GrantTypeConstants.AdminSMSGrantType;
    
            // 系统用户远程调用API,如果是C端用户,那就注入C端用户对应的 API Service
            private readonly ISystemAdminRemoteService _systemAdminRemoteService;
    
            public AdminSMSGrantValidator(ISystemAdminRemoteService systemAdminRemoteService)
            {
                _systemAdminRemoteService = systemAdminRemoteService;
            }
    
            public async Task ValidateAsync(ExtensionGrantValidationContext context)
            {
                try
                {
                    var smsCode = context.Request.Raw.Get("sms_code");
                    var phoneNumber = context.Request.Raw.Get("phone_number");
    
                    if (string.IsNullOrEmpty(smsCode) || string.IsNullOrEmpty(phoneNumber))
                    {
                        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
                        return;
                    }
    
                    // 1-验证码有效性验证
                    if (string.IsNullOrEmpty(phoneNumber))
                    {
                        context.Result = new GrantValidationResult(
                            TokenRequestErrors.InvalidGrant,
                            "短信验证码有误!");
                    }
    
                    // 2-用户有效性验证
                    var remoteResult = await _systemAdminRemoteService.selectAdminByAccount(phoneNumber);
                    if (!remoteResult.IsSuccess)
                    {
                        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest, remoteResult.Msg);
                        return;
                    }
                    else
                    {
                        // Claim 用于配置服务站点 [Authorize("anonymous")]
                        var claimList = new List<Claim>
                            {
                                new Claim("grant_type", GrantTypeConstants.AdminSMSGrantType),
                                new Claim("user_id", remoteResult.Response?.SnowflakeId ?? ""),
                                new Claim("user_name", remoteResult.Response?.AccountName ?? ""),
                                new Claim("name", remoteResult.Response?.AccountName ?? ""),
                                new Claim("role", remoteResult.Response?.RoleId ?? ""),
                                new Claim("phone_number", remoteResult.Response?.Account ?? "")
                            };
    
                        context.Result = new GrantValidationResult(
                            subject: phoneNumber,
                            authenticationMethod: GrantTypeConstants.AdminSMSGrantType,
                            claims: claimList);
                    }
                }
                catch (Exception ex)
                {
                    //context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
                    context.Result = new GrantValidationResult()
                    {
                        IsError = true,
                        Error = ex.Message
                    };
                }
            }
        }
    }
    

    .Net下远程调用API Service如何实现?Java Oauth2认证实现,可私聊交流。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值