準(zhǔn)備
- https://identityserver4.readthedocs.io/en/release/
- OpenID Connect & OAuth 2.0 framework for ASP.NET Core 2.
- 建立Identity Provider項(xiàng)目
- IdentityServer4.Templates
- https://github.com/IdentityServer/IdentityServer4.Templates
- 安裝工具:
dotnet new -i identityserver4.templates- 重置 “dotnet new” 功能列表:
dotnet new --debug:reinit
- 模板:
- dotnet new is4empty
- dotnet new is4ui
- dotnet new is4inmem
dotnet new is4aspid- dotnet new is4ef
- dotnet new is4admin (收費(fèi))
創(chuàng)建項(xiàng)目
dotnet new -i identityserver4.templatesdotnet new is4aspid --name BlogIdp- 升級(jí)為.NET Core 2.1, 更新Nuget包
- 配置Hsts|HttpsRedirection
//注冊(cè)Hsts
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
});
//配置HTTP重定向
services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = 6001;
});

MVC 測(cè)試
- 安全/機(jī)密客戶端(Confidential Client), 它是傳統(tǒng)的服務(wù)器端Web應(yīng)用.
- 它需要長(zhǎng)時(shí)間訪問(wèn)(long-lived access), 所以需要refresh token. 那么它可以使用Authorization Code Flow或Hybrid Flow.
- Hybrid Flow是相對(duì)高級(jí)一些的, 它可以讓客戶端首先從授權(quán)端點(diǎn)獲得一個(gè)ID Token并通過(guò)
瀏覽器(front-channel)傳遞過(guò)來(lái), 這樣我們就可以驗(yàn)證這個(gè)ID Token. 如果驗(yàn)證成功然后, 客戶端再打開(kāi)一個(gè)后端通道(back-channel), 從Token端點(diǎn)獲取Access Token.
-
身份認(rèn)證請(qǐng)求
- 第一行的URI: "/authorize" 就是授權(quán)端點(diǎn)(Authorization Endpoint), 它位于身份提供商(Identity provider, IDP)那里. 這個(gè)URI可以從前面介紹的discovery document里面找到.
- 第二行 response_type=code id_token, 它決定了采取了哪一種Hybrid流程(參考上面那三個(gè)圖).
- 第三行 client_id=xxxx, 這是客戶端的身份標(biāo)識(shí).
- 第四行 redirect_uri=https...., 這是客戶端那里的重定向端點(diǎn)(Redirection Endpoint).
- 第五行 scope=openid profile email, 這就是客戶端所請(qǐng)求的scopes.
-
Hybrid Flow
Hybrid Flow
- 為什么要返回兩次ID Token呢? 這是因?yàn)榈?4)步里面請(qǐng)求Token的時(shí)候要求客戶端身份認(rèn)證, 這時(shí)請(qǐng)求Token的時(shí)候需要提供Authorization Code, Client ID和 Client Secret, 這些secret并不暴露給外界, 這些東西是由客戶端服務(wù)器通過(guò)
后端通道傳遞給Token端點(diǎn)的. 而第一次獲得的ID Token是從前端通道(瀏覽器)返回的. 當(dāng)這個(gè)ID Token被驗(yàn)證通過(guò)之后, 也就證明了當(dāng)前用戶到底是誰(shuí).
code demo
- 新建MVC
- 修改端口為7000,7001
- 修改Idp項(xiàng)目config.cs
new Client
{
ClientId = "mvcclient",
ClientName = "MVC客戶端",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },
RedirectUris = { "http://localhost:7001/signin-oidc" },
FrontChannelLogoutUri = "http://localhost:7001/signout-oidc",
PostLogoutRedirectUris = { "http://localhost:7001/signout-callback-oidc" },
AllowOfflineAccess = true, //offline_access(refresh token)
AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId }// { "openid", "profile", "api1" }
},
- 修改mvc項(xiàng)目Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", options =>
{
options.AccessDeniedPath = "/Authorization/AccessDenied";
})
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:6001";
options.RequireHttpsMetadata = true;
options.ClientId = "mvcclient";
options.ResponseType = "code id_token";
options.Scope.Clear();
options.Scope.Add("openid"); //與Idp中config對(duì)應(yīng)
//options.Scope.Add("profile");
//options.Scope.Add("email");
//options.Scope.Add("restapi");
options.SaveTokens = true;
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
options.GetClaimsFromUserInfoEndpoint = true;
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
- HomeController 測(cè)試
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
......

跳轉(zhuǎn)驗(yàn)證
- 添加更多獲取資源
public async Task<IActionResult> About()
{
var idToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
ViewData["idToken"] = idToken;
return View();
}
- Config.cs添加
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("restapi", "My RESTful API")
};
}
AllowOfflineAccess = true, //offline_access(開(kāi)啟refresh token)
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"restapi"
}// { "openid", "profile", "api1" }
- 修改html
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>IdToken:@ViewData["idToken"]</h3>
<dl>
@foreach (var claim in User.Claims)
{
<dt>@claim.Type</dt>
<dd>@claim.Value</dd>
}
</dl>

保護(hù)API資源
- 安裝包 `IdentityServer4.AccessTokenValidation
- 注冊(cè)
// 注冊(cè)IdentityServer
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:6001";
options.ApiName = "restapi";
});
- config
app.UseAuthentication(); - api中加驗(yàn)證
- 屬性標(biāo)簽
[Authorize] - 全局filter
- 屬性標(biāo)簽
//設(shè)置全局filter保護(hù)api需要認(rèn)真用戶才可訪問(wèn)
services.Configure<MvcOptions>(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
-
NoAuth
401 error - MVC客戶端
- 安裝
IdentityModel - 修改HomeController
public async Task<IActionResult> Contact()
{
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://localhost:5001")
};
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/vnd.enfi.hateoas+json")
);
var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
ViewData["accessToken"] = accessToken;
httpClient.SetBearerToken(accessToken);
var res = await httpClient.GetAsync("api/posts").ConfigureAwait(false);
if (res.IsSuccessStatusCode)
{
var json = await res.Content.ReadAsStringAsync().ConfigureAwait(false);
var objects = JsonConvert.DeserializeObject<dynamic>(json);
ViewData["json"] = objects;
return View();
}
if (res.StatusCode == HttpStatusCode.Unauthorized)
{
return RedirectToAction("AccessDenied", "Authorization");
}
throw new Exception($"Error Occurred:${res.ReasonPhrase}");
}
- 添加 Authorization/AccessDenied
public class AuthorizationController : Controller
{
public IActionResult AccessDenied()
{
return View();
}
}
@{ViewData["Title"] = "AccessDenied";}
<div class="container">
<h2>Access Denied</h2>
</div>
- https://jwt.io 網(wǎng)站解析token



