前言
用戶名密碼模式相較於客戶端憑證模式,多了用戶。通過用戶的用戶名和密碼向Identity Server申請訪問令牌。密碼模式有兩種實現方式.
1.把用戶寫進內存Identity從中讀取賬號密碼驗證
AddInMemoryUsers(config.GetUsers())
2.通過實現 IResourceOwnerPasswordValidator 接口來驗證用戶
AddResourceOwnerValidator(ResourcePasswordValidator)
第二種更加實用靈活,這篇筆記也是實現的第二種。

實現用戶名密碼授權
我們在之前的搭建好的Identity服務上新增一個名為 ResourcePasswordValidator 的類繼承 IResourceOwnerPasswordValidator 重寫ValidateAsync 方法來驗證用戶名和密碼
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Models;
using IdentityServer4.Validation;
namespace IdentityServer
{
public class ResourcePasswordValidator: IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
//判斷賬號密碼是否正確。
if (context.UserName == "userName" && context.Password == "1234567")
{
context.Result = new GrantValidationResult(
subject: "userInfo",
authenticationMethod: OidcConstants.AuthenticationMethods.Password,
claims: GetUserClaims());
}
else
{
//驗證失敗
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
}
}
//可以根據需要設置相應的Claim/需要實現IProfileService接口
private Claim[] GetUserClaims()
{
return new Claim[]
{
new Claim("userId","110"),
new Claim(JwtClaimTypes.Name,"林輝"),
new Claim(JwtClaimTypes.Role,"菜雞")
};
}
}
}
在 Config.cs 中新增一個客戶端
new Client
{
ClientId = "client_b",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
//AccessToken過期時間(秒),默認為3600秒/1小時
AccessTokenLifetime=3600,
//RefreshToken的最長生命周期
//AbsoluteRefreshTokenLifetime = 2592000,
//RefreshToken生命周期以秒為單位。默認為1296000秒
SlidingRefreshTokenLifetime = 2592000,//以秒為單位滑動刷新令牌的生命周期。
//刷新令牌時,將刷新RefreshToken的生命周期。RefreshToken的總生命周期不會超過AbsoluteRefreshTokenLifetime。
RefreshTokenExpiration = TokenExpiration.Sliding,
//AllowOfflineAccess 允許使用刷新令牌的方式來獲取新的令牌
AllowOfflineAccess = true,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "Api"}
}
新建一個 ProfileService 來實現 IProfileService 接口來擴展自定義Claim
using System;
using System.Linq;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Services;
namespace IdentityServer
{
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
//depending on the scope accessing the user data.
var claims = context.Subject.Claims.ToList();
//set issued claims to return
context.IssuedClaims = claims.ToList();
}
catch (Exception ex)
{
//log your error
}
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
}
修改 Startup
//注入DI
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResourceResources())
.AddInMemoryApiResources(Config.GetApiResources())//Api資源信息
.AddInMemoryClients(Config.GetClients())//客戶端信息
.AddResourceOwnerValidator<ResourcePasswordValidator>()//用戶驗證
.AddProfileService<ProfileService>();//擴展claims
測試效果
通過用戶名密碼申請令牌

當access_token過期的時候通過refresh_token來刷新access_token,refresh_token只能使用一次,每次刷新后會返給信的refresh_token和access_token

我們通過jwt.io解析出來。可以發現jwt里面包含了我們添加的身份信息,這些信息可以直接在資源服務器中獲取使用

修改資源服務器
我們在Api中可以通過 User.Claims.FirstOrDefault(m => m.Type == "userId").value; 獲取我們用戶身份信息。
[HttpGet("userInfo")]
[Authorize]
public ActionResult UserIno()
{
return new JsonResult($"用戶id{User.Claims.FirstOrDefault(m => m.Type == "userId").Value }" );
}

