基于IdentityServer4示例的修改 - zLulus/My_Note GitHub Wiki

自己的业务与需要修改的内容

1.Client,Api Resource,Identity Resource从内存存储改为EF存储
2.将一个Client改为多个Client
3.将邮箱账号改为手机号账号
4.Api需要增加一个查询用户信息的接口,提供给Client

具体实施

Client,Api Resource,Identity Resource从内存存储改为EF存储

在IdentityServer项目的Startup.cs中进行数据库配置

public void ConfigureServices(IServiceCollection services)
{
	const string connectionString = @"数据库连接字符串";
	var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

	services.AddIdentityServer()
		.AddDeveloperSigningCredential()
		.AddTestUsers(Config.GetUsers())
		//从数据库中添加配置数据 (clients, resources)
		.AddConfigurationStore(options =>
		{
			options.ConfigureDbContext = builder =>
				builder.UseSqlServer(connectionString,
					sql => sql.MigrationsAssembly(migrationsAssembly));
		})
		//从数据库中添加操作数据 (codes, tokens, consents)
		.AddOperationalStore(options =>
		{
			options.ConfigureDbContext = builder =>
				builder.UseSqlServer(connectionString,
					sql => sql.MigrationsAssembly(migrationsAssembly));

			// this enables automatic token cleanup. this is optional.
			options.EnableTokenCleanup = true;
			options.TokenCleanupInterval = 30;
		});
}

将一个Client改为多个Client

不同的Client,配置的时候,ClientId不同
其对应的ClientSecret,Scope也不同
注意:
在客户端的授权功能要求一致的情况下,也可以多个客户端使用同一个ClientId,比如单点登录,所有客户端要做的都是登录
则在IdentityServer眼中,认为两个客户端是相同的,不过不影响业务流程(登录)

将邮箱账号改为手机号账号

将前端校验和数据库数据结构约束改为手机号即可

Api需要增加一个查询用户信息的接口,提供给Client

IdentityServer4取消了原来的IUserService,将其拆分成了IResourceOwnerPasswordValidator和IProfileService,需要实现接口
IResourceOwnerPasswordValidator处理资源所有者密码凭证的验证,IProfileService允许IdentityServer连接到用户和配置文件存储

public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
    //用户表仓储
    private readonly IUserRepository _userRepository;

    public ResourceOwnerPasswordValidator(IUserRepository userRepository)
    {
        _userRepository = userRepository; //DI
    }

    //验证你的用户帐户,并提供令牌
    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        try
        {
            var user = await _userRepository.FindAsync(context.UserName);
            if (user != null)
            {
                //核实密码
                if (user.Password == context.Password) {
                    context.Result = new GrantValidationResult(
                        subject: user.UserId.ToString(),
                        authenticationMethod: "custom", 
                        claims: GetUserClaims(user));
                    return;
                } 

                context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Incorrect password");
                return;
            }
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "User does not exist.");
            return;
        }
        catch (Exception ex)
        {
            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid username or password");
        }
    }

    //从用户数据中构建claims
    public static Claim[] GetUserClaims(User user)
    {
        return new Claim[]
        {
            new Claim("user_id", user.UserId.ToString() ?? ""),
            new Claim(JwtClaimTypes.Name, (!string.IsNullOrEmpty(user.Firstname) && !string.IsNullOrEmpty(user.Lastname)) ? (user.Firstname + " " + user.Lastname) : ""),
            new Claim(JwtClaimTypes.GivenName, user.Firstname  ?? ""),
            new Claim(JwtClaimTypes.FamilyName, user.Lastname  ?? ""),
            new Claim(JwtClaimTypes.Email, user.Email  ?? ""),
            new Claim("some_claim_you_want_to_see", user.Some_Data_From_User ?? ""),

            //roles
            new Claim(JwtClaimTypes.Role, user.Role)
        };
}
public class ProfileService : IProfileService
{
    private readonly IUserRepository _userRepository;

    public ProfileService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    //调用/connect/userinfo时,根据声明获取用户配置文件日期
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        try
        {
            //取决于访问用户数据的范围
            if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
            {
                //从数据库中查询用户
                var user = await _userRepository.FindAsync(context.Subject.Identity.Name);

                if (user != null)
                {
                    var claims = GetUserClaims(user);

                    //返回结果
                    context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
                }
            }
            else
            {
                //从context获得subject (在ResourceOwnerPasswordValidator.ValidateAsync中设置的)
                var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");

                if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
                {
                    //查询用户
                    var user = await _userRepository.FindAsync(long.Parse(userId.Value));

                    if (user != null)
                    {
                        var claims = ResourceOwnerPasswordValidator.GetUserClaims(user);

                        context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
                    }
                }
            }
        }
        catch (Exception ex)
        {
            //记录错误
        }
    }

    //核实用户的IsActive是否为true
    public async Task IsActiveAsync(IsActiveContext context)
    {
        try
        {
            //从context获得subject (在ResourceOwnerPasswordValidator.ValidateAsync中设置的),
            var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "user_id");

            if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
            {
                var user = await _userRepository.FindAsync(long.Parse(userId.Value));

                if (user != null)
                {
                    if (user.IsActive)
                    {
                        context.IsActive = user.IsActive;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            //记录错误
        }
    }
}

在Startup.cs里面定义

public void ConfigureServices(IServiceCollection services)
{
    //添加仓储
    services.AddScoped<IUserRepository, UserRepository>();

    services.AddIdentityServer()
        //省略了一些内容
        //添加个人资料服务
        .AddProfileService<ProfileService>();

    //注入服务
    services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
    services.AddTransient<IProfileService, ProfileService>();
}

参考资料:
https://stackoverflow.com/questions/35304038/identityserver4-register-userservice-and-get-users-from-database-in-asp-net-core/
/connect/userinfo接口说明:
http://docs.identityserver.io/en/release/endpoints/userinfo.html#identitymodel