Session認證和JWT(Json Web Token)
Token認證就是基於JWT
1.Session認證
1. 用戶輸入其登錄信息
2. 服務器驗證信息是否正確,並創建一個session,然后將其存儲在數據庫中
3. 服務器為用戶生成一個sessionId,將具有sesssionId的Cookie將放置在用戶瀏覽器中
4. 在后續請求中,會根據數據庫驗證sessionID,如果有效,則接受請求
5. 一旦用戶注銷應用程序,會話將在客戶端和服務器端都被銷毀
2.JWT認證
1. 用戶輸入其登錄信息
2. 服務器驗證信息是否正確,並返回已簽名的token
3. token儲在客戶端,例如存在local storage或cookie中
4. 之后的HTTP請求都將token添加到請求頭里
5. 服務器解碼JWT,並且如果令牌有效,則接受請求
6. 一旦用戶注銷,令牌將在客戶端被銷毀,不需要與服務器進行交互一個關鍵是,令牌是無狀態的。后端服務器不需要保存令牌或當前session的記錄。
JWT里面只存放了一些不敏感的信息 比如一些重要的信息是用戶發起請求 驗證身份成功 再去 ProfileService 中把敏感信息放到HttpContext中 比如租戶ID 等
服務器不保存生成Token Token由三部分組成 根據客戶端請求的Token 根據Token的前部和中部和密鑰計算出來跟尾部相同 就驗證通過 節約了服務器內存
http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
用VS 直接運行就報錯 但是用VS Core打開 用dotnet run命令運行就沒有報這個錯誤
IdentityServer4配置成功后 配置頁面 localhost:xxxx/.well-known/openid-configuration

身份驗證服務器需要添加IdentityServer4 包
客戶端只需要添加IdentityServer4.AccessTokenValidation 包
進銷存用的是密碼模式
相當於經銷存和protal protal是身份驗證服務器
經銷存只是一個客戶端 只需要添加IdentityServer4.AccessTokenValidation

1.用戶在第三方客戶端上操作的時候 想要訪問接口地址 先去授權服務器認證
2.授權服務器通過驗證后 會通過ProfileService 把用戶信息包裝成一個Claim 放返回
3.請求會加上返回的Claim 里面有用戶信息 資料 token
注:如果是通過Postman測試 下面這樣傳參沒問題 如果是通過前端來獲取token 會報錯400 Bad Request
都是post接口 通過抓包軟件分析 傳遞的參數是

在前端需要對參數進行格式化 轉成字符串 然后把Content-type改成application/x-www-form-urlencoded
var param = '';
let data = {
client_id:'Consoleclient',
client_secret:'511536EF-F270-4058-80CA-1C89C192F69A',
grant_type:'client_credentials'
};
for (let property in data) {
param += `&${property}=${data[property]}`
}
param = param.substring(1, param.length)
this.apiService.identityLoginAPI(param).then(res => {
Claim是用戶身份 一個用戶可以有多個身份
Scope是API資源的權限
1.客戶端模式
進銷存前端需要調用后端API 需要先到protal 的IdentityServer服務器 經過授權 拿到token 才能訪問到后端API
獲取token 傳參方式

2.密碼模式
需要輸入真實賬戶和密碼 這種模式body 可以設置為form-data 也可以設置為x-www-form-urlencoded

3.授權碼模式
做一個統一的登陸界面 比如Pc端在Web上進行QQ登陸 然后比如登陸斗魚的時候 可以用QQ賬號密碼登陸 也會跳轉到QQ登陸的統一界面
搭建身份驗證服務器
.Net Core的項目模版生成出來后 默認是https訪問 在properties中的launchSettings.json中把sslport改為0
"iisExpress": {
"applicationUrl": "http://localhost:5000",
"sslPort": 0
}
1.新建一個空web Api項目
添加IdentityServer4包 這里用的版本都是2.1
添加Config.cs配置類信息
這是客戶端模式配置
public class Config
{
/// <summary>
/// 配置資源服務器
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetResource()
{
return new List<ApiResource>
{
new ApiResource("api","My Api")
};
}
/// <summary>
/// 配置客戶端
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client()
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets ={
new Secret("secret".Sha256())
},
AllowedScopes = { "api"}
}
};
}
}
2.把IdentityServer添加到依賴注入配置項
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
//添加開發證書
.AddDeveloperSigningCredential()
//添加資源Api
.AddInMemoryApiResources(Config.GetResource())
//添加客戶端
.AddInMemoryClients(Config.GetClients());
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
3.在管道中添加身份驗證服務器中間件
app.UseIdentityServer();
4.啟動的時候可能會報錯 第一次配報錯 第二次配沒有報錯 如果報錯用Vs Code 打開 用dotnet run 命令運行
進入http://localhost:5000/.well-known/openid-configuration查看配置
"token_endpoint":"http://localhost:5000/connect/token", 這個地址就是獲取token的地址

到這里 身份驗證服務器搭建完畢 后面開始搭建客戶端服務器 也就是接口服務器
1.新建一個Web Mvc空項目
添加IdentityServer4.AccessTokenValidation包
注入是身份驗證服務器 這里的地址就是上面配的驗證服務器地址
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(Options =>
{
Options.Authority = "http://localhost:5000";
Options.RequireHttpsMetadata = false;
Options.ApiName = "api";
});
2.在管道中開啟授權驗證中間件
app.UseAuthentication();
3.在需要保護的接口上添加[Authorize]特性
4.直接訪問現在的接口 就會報錯401 沒有授權
先通過http://localhost:5000/connect/token 身份驗證服務器拿到token 再去訪問api接口
第二種 帳號密碼模式
修改驗證服務器的Config.cs
public class Config
{
public static IEnumerable<ApiResource> GetResource()
{
return new List<ApiResource>
{
new ApiResource("api","My Api")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
//客戶端模式
new Client()
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets ={
new Secret("secret".Sha256())
},
AllowedScopes = { "api"}
},
//賬號密碼模式
new Client()
{
ClientId = "pwdClient",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets ={
new Secret("secret".Sha256())
},
AllowedScopes = { "api"}
}
};
}
/// <summary>
/// 添加一個模擬的帳號密碼
/// </summary>
/// <returns></returns>
public static List<TestUser> GetTestUsers()
{
return new List<TestUser>{
new TestUser{
SubjectId = "1",
Username="jcb",
Password = "123456"
}
};
}
}
2.把賬號密碼模式也進行注入 修改Startup.cs
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetResource())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers());
這樣就完成了帳號密碼模式的配置
把賬號密碼模式改為動態的 通過數據庫進行匹配
1.新增登錄驗證器類LoginValidator 繼承IResourceOwnerPasswordValidator 接口有一個驗證方法ValidateAsync
public class LoginValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
Customers customers = new Customers() { Name = context.UserName };
var res = await Login(customers);
if (res != null)
{
//GrantValidationResult實例根據構造函數傳參不同來進行判斷是否成功
context.Result = new GrantValidationResult(res.Id.ToString(), "password", (IEnumerable<Claim>)null, "local", (Dictionary<string, object>)null);
}
else
{
}
}
//登錄驗證可以寫這里
public async Task<Customers> Login(Customers customer)
{
using (MyDbContext db = new MyDbContext())
{
var res = db.customers.FirstOrDefault(t=>t.Name == customer.Name);
if (res != null)
{
return res;
}
return null;
}
}
}
2.在注冊身份驗證服務器的時候 把這個登錄驗證添加進來
.AddResourceOwnerValidator<LoginValidator>();
3.這樣子執行的時候 默認會使用自帶的DefaultProfileService 執行到內部IsActiveAsync()方法會報錯 這個類在C:\Users\jiangchengbiao\.nuget\packages\identityserver4\2.1.0\lib\netstandard2.0\IdentityServer4.dll
解決方式:需要新建一個類 繼承IProfileService 重寫里面的IsActiveAsync() 然后再把這個注冊到身份驗證服務器上
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//通過第一次驗證輸出的賬號密碼 查詢到租戶ID等其他信息 添加到身份信息中
List<Claim> claims = Enumerable.ToList<Claim>(context.Subject.Claims);
claims.Add(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "蔣承標"));
claims.Add(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "浙江工貿"));
claims.Add(new Claim("http://jcb.org/identity/claims/tenantId", "租戶ID"));
context.IssuedClaims = claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
int num = await(Task<int>) Task.FromResult<int>(0);
}
}
4.把重寫的ProfileService 注冊到身份驗證服務器上
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetResource())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetTestUsers())
.AddProfileService<ProfileService>()
.AddResourceOwnerValidator<LoginValidator>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
到這里就完成了 可以根據輸入的帳號密碼到數據庫進行比對
然后在客戶端(也就是經銷商系統)啟動JWT中間件 參數一定要是這個 還沒具體去看
app.UseJwtTokenMiddleware("IdentityBearer");
ctx.AuthenticateAsync會去身份驗證服務器中把 各種身份信息查詢出來 這里面有租戶ID 用戶姓名等 放到HttpContext中
public static class JwtTokenMiddleware
{
public static IApplicationBuilder UseJwtTokenMiddleware(this IApplicationBuilder app, string schema = JwtBearerDefaults.AuthenticationScheme)
{
return app.Use(async (ctx, next) =>
{
if (ctx.User.Identity?.IsAuthenticated != true)
{
var result = await ctx.AuthenticateAsync(schema);
if (result.Succeeded && result.Principal != null)
{
ctx.User = result.Principal;
}
}
await next();
});
}
}
使用OpenID Connect添加用戶認證
1.客戶端安裝Nuget包
IdentityServer4.AccessTokenValidation
Microsoft.AspNetCore.Authentication.Abstractions
Microsoft.AspNetCore.Authentication.Cookies
Microsoft.AspNetCore.Authentication.OpenIdConnect
客戶端代碼
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolDbContext>(d=>d.UseSqlServer(Configuration.GetConnectionString("Default")));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvc";
options.SaveTokens = true;
});
services.AddMvc();
}
