鼎鼎知识库
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

20IdentityServer在API中的实现.md 9.3KB

在研究IdentityServer4的过程中遇到过两个问题:

  • 在IdentityServer4通常的资料是针对Web的,而不是通过接口
  • .Net Core 2.1 2.0有些地方和.NET和.Net Core 2.0都不一样

正好发现一个项目是在 .Net Core写,并且IdentityServer4提供接口。项目地址:https://github.com/marklaygo/Angular-IdentityServer-WebAPI

首先来看Config.cs

public static class Config
{
    //User
    public static List<TestUser> GetUsers()
    {
        return new List<TestUser>{
            new TestUser{
                SubjectId="1",
                Username="",
                Password=""
            }
        };
    }

    //IdentityResource
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new IdentityResource[]{
            new IdentityResources.OpenId(),
            new IdentityResources.Profile();
        };
    }

    //API
    public static IEnumerable<ApiResource> GetApis()
    {
        return new ApiResource[]{
            new ApiResource("api1", "webApi"),
            new ApiResource("accountApi", "AccountApi") //这里的api给验证服务器自己用,因为验证服务器通过接口开发给外界
        };
    }

    //Client
    public static IEnumerable<Client> GetClients()
    {
        renturn new Client[]
        {
            new Client
            {
                ClientId="Angular",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                RequireClientSecret = fasle,
                AllowAccessTokenViaBrowser = true,
                AccessTokenLifetime = 300,
                AllowOfflineAccess = true,
                AllowedScopes = {
                    IdentityServerConstants.StandardScopes.OpenId,//客户端要的和IdentityServer4给的保持一致
                    IdentityServerConstatns.StandardScopes.Profile,//客户端要的和IdentityServer4给的保持一致
                    "api1",//api告诉Client
                    "accountApi"//api告诉Client
                }
            }
        };
    }
}

再来看上下文,一定是继承IdentityDbContext

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options){

    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}

public class ApplicationUser : IdentityUser
{

}

有关对外开放的claim,通过实现IProfileService接口。

public class IdentityProfileSerivce  : IProfileService
{
    private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
    private readonly UserManager<ApplicationUser> _userManager;

    public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicaitonUser> claimsFactory, UserManager<ApplicationUser> userManager)
    {
        _claimsFactory = claimsFactory;
        _userManager = userManager;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var subjectId = context.Subject.GetSubjectId();
        var user = awati _userManager.FindByIdAsync(subjectId);
        var principal = await _claimsFactory.CreateAsync(user);
        var claims = principal.Claims.ToList();
        claims.Add(new Claim(JwtClaimTypes.Role, "test_role"));
        context.IssuedClaims = claim;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var subjectId = context.Subject.GetSubjectId();
        var user = awati _userManager.FindByIdAsync(subjectId);
        context.IsActive = user != null;
    }
}

Startup.cs配置文件

public class Startup
{
    public IConfiguration Configuration{get;}
    public IHostingEnvironment Environment{get;}

    public Startup(IConfiguraiotn configuraiotn, IHostingEnvironment environment)
    {
        Configuraiton = configuration;
        Envrionment = environment;
    }

    public void ConfigureService(IServiceCollection services)
    {
        //上下文
        services.AddDbContext<ApplicationDbContext>(options => options.UserSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        //Identity
        servcies.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkeStore<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        //有关Password的配置
        services.COnfigure<IdentityOptions>(options => {
            options.Password.RequrieDigit = false;
            options.Password.RequrieLength =4;
            options.Password.ReqruieNonAlphanumeric = false;
            options.Password.RequireUppercase = false;
            options.Password.RequrieLowercase = false;
        })

        //mvc
        services.AddMvcCore()
            .AddAuthorization()
            .AddJsonFormatters();

        //对外开发的claim
        services.AddTransient<IProfileService, IdentityProfileService>();

        //http://localhost:5000验证服务器的地址
        //验证服务器的配置
        var builder = service.AddIdentityServer()
            .AddInMemoryIndentityRecouse(Config.GetIdenityResource())
            .AddInMemoryApiResource(Config.GetApis())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>()
            .AddProfileService<IdentityProfileService>();

        //验证服务器接口本身也需要验证
        services.AddAuthetnication("Bearer")
            .AddJwtBearer("Bearer", options => {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
                options.Audience = "accountApi";
                options.TokenValidatioinParamsters = new TokenValidationParameters()
                {
                    ClockSkew = TimeSpan.FromMinutes(0)
                }
            });

        //cors
        services.AddCors(options => {

                options.AddPolicy("default", policy => {
                    policy.WithOrigin("http://localhost:4200")
                        .AllowAnyHeader()
                        .AllowAnyMethod();
                });
        });

        if(Environment.IsDevelopment())
        {
            builder.AddDeveloperSigningCredential();
        }
        else
        {
            throw new Exception("need to configure key material");
        }

    }

    public void Configure(IApplicationBuilder app, ApplicationDbContext context)
    {
        if(Environment.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseCors("default");
        app.UseIdentityServer();
        app.UseAuthentication();
        app.UseMvc();

        //种子化数据
        try
        {
            context.Database.Migrate();
            context.Users.RemoveRange(contexts.Users.ToList());
            context.SaveChanges();

            var user = new ApplicationUser
            {
                UserName = "",
                NormalizedUserName = "",
                Email = "",
                NormalizedEmail = "",
                EmailConfirmed = true,
                LockoutEnabled = fasle,
                SecurityStamp = Guid.NewGuid().ToString()
            };

            if(!context.Users.Any(u =>u.UserName == user.UserName))
            {
                var passwordHasher = new PasswordHasher<ApplicatonUser>();
                var hased = passwordHasher.HashPassword(user,"");
                user.PasswordHash = hased;
                var userStore = new UserStore<ApplicationUser>(context);
                userStore.CreateAsync(user);
            }
        }
        catch(Exception ex)
        {

        }
    }
}

模型

public class RegisterViewModel
{
    [Required]
    [EmailAddress]
    publci string Email{get;set;}

    [Requried]
    [DataType(DataType.Password)]
    public string Password{get;set;}
}

public class ChangePasswordViewModel
{
    [Required]
    [DataType(DataType.EmailAddress)]
    public string Email{get;set;}

    [Required]
    [DataType(DataType.Password)]
    public string OldPassword{get;set;}

    [Requried]
    [DataType(DataType.Password)]
    public string NewPassword{get;set;}
}

最后来到有关用户管理的控制器。

[Route("api/[controller]")]
publci class AccountController : ControllerBase
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly IUserClaimPrincipalFactory<ApplicatioinUser> _claimsFactory;

    //构造函数略

    [HttpPost("Register")]
    public async Task<IActionResult> Register([FromBody]RegisterViewModel model)
    {
        if(ModelState.IsValid)
        {
            var user = new ApplicationUser {UserName = model.Email, Email = model.Email};
            var result = awati _userManager.CreateAsync(user, model.Password);
            return new JsonResult(result);
        }
        return BadRequest();
    }

    [HttpPost("ChangePassword")]
    [Authorize(AuthenticationSchemes = "Bearer")]
    public async Task<IActionResult> ChangePassword([FromBody]ChangePasswordViewModel model)
    {
        if(ModelState.IsValid)
        {
            var user = await _userManager.FindByEmailAsync(model.Email);
            if(user!=null)
            {
                var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
                return new JsonResult(result);
            }
        }
        return BadRequest();
    }
}