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(); }); } }
授權碼模式
1.比如登錄斗魚,選擇QQ登錄,跳轉到QQ統一登錄頁面並攜帶returnUrl
2.輸入賬號密碼,同意授權,頁面會回到returnUrl並攜帶Authorization Core
3.斗魚向自己的服務發起登錄請求傳入Authorization Core
4.斗魚后台服務通過Authorization Core參數向QQ發起獲取AccessToken
5.獲取AccessToken后,在向QQ發起獲取用戶信息
6.通過QQ返回的用戶信息創建斗魚賬號,創建斗魚服務頒發的Token返回給瀏覽器
第4-6步都是在后台完成的
open id connect
在授權碼模式的第2步,QQ在返回的時候會直接返回用戶信息和Access token,免去了后面的環節
使用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(); }
