IdentityServer4 4.x版本 配置Scope的正確姿勢
阿新 • • 發佈:2020-07-03
# 前言
> IdentityServer4 是為ASP.NET Core系列量身打造的一款基於 OpenID Connect 和 OAuth 2.0 認證的框架
IdentityServer4官方文件:https://identityserver4.readthedocs.io/
看這篇文章前預設你對IdentityServer4 已經有一些瞭解。
本篇使用IdentityServer4的4.x版本,跟老版本的稍微有些差別。下面直接進入正題。
# 鑑權中心
## 建立IdentityServer4專案
使用IdentityServer4 來搭建一個鑑權中心,首先建議安裝一下IdentityServer4的官方專案模板。也可以不安裝,自己建立專案,然後NuGet安裝需要的包也行。(不過還是推薦用官方的模板,很方便)。
命令列執行:`dotnet new -i IdentityServer4.Templates`
![image-20200629205619088](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120419.png)
安裝完成後會多出以下專案模板:
![image-20200629205731577](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120429.png)
我這裡選用is4inmem這個模板來建立專案,這個模板的資料都是寫死在記憶體中的,並且包含了Quickstart頁面,比較簡單方便。
來到我的專案目錄下執行:`dotnet new is4inmem --name Idp`
![image-20200701190325246](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120433.png)
執行完成會生成以下檔案:
![image-20200701195853822](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120437.png)
VS2019開啟專案:
![image-20200701195955107](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120441.png)
執行專案:
![image-20200701200225015](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120444.png)
## 配置ApiResource、ApiScope、Clients
修改Startup:
```csharp
// in-memory, code config
builder.AddInMemoryIdentityResources(Config.IdentityResources);
builder.AddInMemoryApiScopes(Config.ApiScopes);
//新增API資源
builder.AddInMemoryApiResources(Config.ApiResources);
builder.AddInMemoryClients(Config.Clients);
```
這裡比之前版本多了一個新增ApiScopes的方法:
`builder.AddInMemoryApiScopes(Config.ApiScopes);`
因為我接下來有要保護的API資源,所以需要新增一行:
`builder.AddInMemoryApiResources(Config.ApiResources);`
Config中的程式碼:
```csharp
public static class Config
{
public static IEnumerable IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};
public static IEnumerable ApiScopes =>
new ApiScope[]
{
new ApiScope("scope1"),
//new ApiScope("scope2"),
};
public static IEnumerable ApiResources =>
new ApiResource[]
{
new ApiResource("api1","#api1")
{
//!!!重要
Scopes = { "scope1"}
},
//new ApiResource("api2","#api2")
//{
// //!!!重要
// Scopes = { "scope2"}
//},
};
public static IEnumerable Clients =>
new Client[]
{
new Client
{
ClientId = "postman client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("postman secret".Sha256()) },
AllowedScopes = { "scope1" }
},
};
}
```
我添加了一個ID為postman client的客戶端,授權模式就用最簡單的ClientCredentials客戶端模式。需要注意的是4.x版本的ApiScope和ApiResource是分開配置的,然後在ApiResource中一定要新增Scopes。如果你在網上搜的IdentityServer4教程比較老的,都是沒有這個ApiScope的,預設ApiResource的Name作為Scope。類似這樣:
```
public static IEnumerable ApiResources =>
new ApiResource[]
{
new ApiResource("api1","#api1"),//錯誤
new ApiResource("api2","#api2"),//錯誤
};
public static IEnumerable Clients =>
new Client[]
{
new Client
{
......
AllowedScopes = { "api1", "api2" }
},
};
```
如果你這麼寫的話,雖然不影響你獲取token,但是你訪問api資源的話,永遠會得到一個401錯誤!!!
# ApiResource
下面新增一個api1資源,新建asp.netcore web應用並使用webapi模板:
![image-20200701211036365](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120453.png)
NuGet安裝:`Microsoft.AspNetCore.Authentication.JwtBearer`
Startup部分程式碼:
```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//IdentityServer地址
options.Authority = "http://localhost:5001";
//對應Idp中ApiResource的Name
options.Audience = "api1";
//不使用https
options.RequireHttpsMetadata = false;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
//身份驗證
app.UseAuthentication();
//授權
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
```
給WeatherForecastController新增`[Authorize]`標記:
![image-20200701214601854](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120458.png)
執行Api1Resource,用postman測試訪問weatherforecast介面:
![image-20200701214742071](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120502.png)
此時得到401錯誤。下面先去Idp獲取一個token:
![image-20200701215031535](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120505.png)
拿到token後再去訪問weatherforecast就沒問題了:
![image-20200701215748634](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120509.png)
進行到這裡,好像跟scope都沒什麼關係,那麼scope到底有什麼用處呢?
# ApiScope策略授權
繼續修改程式碼。
Api1Resource專案NuGet安裝:IdentityServer4.AccessTokenValidation
![image-20200701221017612](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120514.png)
再新建一個TestController用於區分:
![image-20200701223359517](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120517.png)
下面我需要做的是使用scope結合策略授權來分別限制TestController和WeatherForecastController的訪問許可權。
修改Startup:
```csharp
public void ConfigureServices(IServiceCollection services)
{
......
services.AddAuthorization(options =>
{
//基於策略授權
options.AddPolicy("WeatherPolicy", builder =>
{
//客戶端Scope中包含api1.weather.scope才能訪問
builder.RequireScope("api1.weather.scope");
});
//基於策略授權
options.AddPolicy("TestPolicy", builder =>
{
//客戶端Scope中包含api1.test.scope才能訪問
builder.RequireScope("api1.test.scope");
});
});
}
```
為了好理解,我把scope名稱分別改成了:api1.weather.scope和api1.test.scope。
WeatherForecastController的Authorize標記修改一下:`[Authorize(Policy = "WeatherPolicy")]`
TestController的程式碼很簡單:
![image-20200701224046637](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120521.png)
因為修改了scope名稱,需要把Idp中的scope名稱也改一下:
```
public static IEnumerable ApiScopes =>
new ApiScope[]
{
new ApiScope("api1.weather.scope"),
new ApiScope("api1.test.scope"),
//new ApiScope("scope2"),
};
public static IEnumerable ApiResources =>
new ApiResource[]
{
new ApiResource("api1","#api1")
{
//!!!重要
Scopes = { "api1.weather.scope", "api1.test.scope" }
},
//new ApiResource("api2","#api2")
//{
// //!!!重要
// Scopes = { "scope2"}
//},
};
```
客戶端定義,AllowedScopes暫時只給一個api1.weather.scope測試一下
```
public static IEnumerable Clients =>
new Client[]
{
new Client
{
ClientId = "postman client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("postman secret".Sha256()) },
AllowedScopes = { "api1.weather.scope" }
},
};
```
postman獲取token:
![image-20200701225242813](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120538.png)
訪問weatherforecast介面,正常響應200。
![image-20200701225430395](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120541.png)
再訪問test,得到403錯誤:
![image-20200701225508071](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120544.png)
接下來修改一下Idp的客戶端定義,新增api1.test.scope:
`AllowedScopes = { "api1.weather.scope", "api1.test.scope" }`
修改Idp後一定要重新獲取token,jwt就是這樣,一旦生成就無法改變。
![image-20200701230022811](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120548.png)
拿到新的token後訪問test和weatherforecast,這時候就都可以正常響應了。
![image-20200701230107290](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120551.png)
![image-20200701230209695](https://xhznl-blogs-bucket.oss-cn-beijing.aliyuncs.com/images/20200702120554.png)
# 總結
以上使用IdentityServer4搭建了一個鑑權中心,保護API資源,並使用ApiScope配合策略授權完成了一個簡單的許可權控制。IdentityServer4的玩法非常多,知識點也很多。強烈推薦B站的@[solenovex](https://space.bilibili.com/361469957) 楊老師的視訊,地址:https://www.bilibili.com/video/BV16b411k7yM 多看幾遍,會有收穫。。。
需要程式碼的點這裡:https://github.com/xiajingren/IdentityServer4-4.x-Sco