IdentityServer4 (1) 客戶端受權模式(Client Credentials)

寫在前面

一、源碼(.Net Core 2.2)

  git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.githtml

二、相關章節

  2.一、《IdentityServer4 (1) 客戶端受權模式(Client Credentials)
  2.二、《IdentityServer4 (2) 密碼受權(Resource Owner Password)
  2.三、《IdentityServer4 (3) 受權碼模式(Authorization Code)》
  2.四、《IdentityServer4 (4) 靜默刷新(Implicit)》
  2.五、《IdentityServer4 (5) 混合模式(Hybrid)》git

三、參考資料

  IdentityServer4 中文文檔 http://www.identityserver.com.cn/
  IdentityServer4 英文文檔 https://identityserver4.readthedocs.io/en/latest/github

四、流程圖

  客戶端受權模式是最基本的使用場景,咱們須要作一個API(受保護的資源),一個客戶端(訪問的應用),一個IdentityServer(用來受權)json

  

1、建立IdentityServer

一、用VS建立一個Web 項目

  

二、添加引用 IdentityServer4 包,下圖是我已經安裝好了的截圖

三、添加一個配置文件(這裏也能夠使用json文件)

    public class IdpConfig
    {
        /// <summary>
        /// 用戶認證信息
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<IdentityResource> GetApiResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Address(),
                new IdentityResources.Email(),
                new IdentityResources.Phone()
            };
        }
        /// <summary>
        /// API 資源
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApis()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1", "My API") 
            };
        }

        /// <summary>
        /// 客戶端應用
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    // 客戶端ID 這個很重要
                    ClientId = "client",
                    //AccessToken 過時時間,默認3600秒,注意這裏直接設置5秒過時是無論用的,解決方案繼續看下面 API資源添加JWT
                    //AccessTokenLifetime=5, 
                    // 沒有交互性用戶,使用 clientid/secret 實現認證。
                    AllowedGrantTypes = GrantTypes.ClientCredentials, 
                    // 用於認證的密碼
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    // 客戶端有權訪問的範圍(Scopes)
                    AllowedScopes = { "api1" }
                }
            };
        }
    }

四、在StartUp.cs 裏註冊 IdentityServer4 

  ConfigureServices()api

    services.AddIdentityServer(options =>
    {
        options.Events.RaiseErrorEvents = true;
        options.Events.RaiseInformationEvents = true;
        options.Events.RaiseFailureEvents = true;
        options.Events.RaiseSuccessEvents = true;
    })
      .AddDeveloperSigningCredential()//解決Keyset is missing 錯誤
      //.AddTestUsers(TestUsers.Users)
      //.AddInMemoryIdentityResources(IdpConfig.GetApiResources())
      .AddInMemoryApiResources(IdpConfig.GetApis())
      .AddInMemoryClients(IdpConfig.GetClients());

  Configure()方法添加使用 IdentityServer4 中間件緩存

app.UseIdentityServer();

五、配置完成

  啓動項目,訪問 http://localhost:5002/.well-known/openid-configuration (個人端口號是5002) ,能夠瀏覽 發現文檔,參考下圖,說明已經配置成功。app

  後面客戶端會使用裏面的數據進行請求tokeasync

  項目第一次啓動根目錄也會生成一個文件 tempkey.rsaide

   

2、客戶端

一、新建一個.Net Core Web 項目

  這裏能夠使用其餘創建客戶端 。例如:控制檯程序、wpf 等等。須要添加 NuGet 包 IdentityModel測試

  

二、新建一個 Controller 用來測試訪問上面的IdentityServer

  獲取token,訪問 http://localhost:5003/Idp/token ,提示訪問成功

    public class IdpController : Controller
    { 
        private static readonly string _idpBaseUrl = "http://localhost:5002"; 
        public async Task<IActionResult> Token()
        {
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync(_idpBaseUrl);
            if (disco.IsError)
            {
                return Content("獲取發現文檔失敗。error:" + disco.Error);
            }
            var token = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
            {
                Address = disco.TokenEndpoint,
                //ClientId、ClientSecret、Scope 這裏要和 API 裏定義的Client如出一轍
                ClientId = "client",
                ClientSecret = "secret",
                Scope = "api1"
            });
            if (token.IsError)
            {
                return Content("獲取 AccessToken 失敗。error:" + disco.Error);
            } 
            return Content("獲取 AccessToken 成功。Token:" + token.AccessToken);
        }  
    }

3、添加API資源

一、新建一個API項目

  我把API項目和IdentityServer 放到同一個解決方案,這個本身定,無所謂的

  API資源指的是IdentityServer IdpConfig.GetApis() 裏面添加的 api1(這個api1名稱隨便起,可是要注意必定要保持一致)

  添加認證以後就能夠測試用 AccessToken 請求資源了

二、添加JWT 認證

  StartUp.ConfigureServices()

       services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
       .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
       {
           // IdentityServer 地址
           options.Authority = "http://localhost:5002";
           //不須要https
           options.RequireHttpsMetadata = false;
           //這裏要和 IdentityServer 定義的 api1 保持一致
           options.Audience = "api1";
           //token 默認容忍5分鐘過時時間偏移,這裏設置爲0,
           //這裏就是爲何定義客戶端設置了過時時間爲5秒,過時後仍能夠訪問數據
           options.TokenValidationParameters.ClockSkew = TimeSpan.Zero;
           options.Events = new JwtBearerEvents
           {
               //AccessToken 驗證失敗
               OnChallenge = op =>
               {
                   //跳過全部默認操做
                   op.HandleResponse();
                   //下面是自定義返回消息
                   //op.Response.Headers.Add("token", "401");
                   op.Response.ContentType = "application/json";
                   op.Response.StatusCode = StatusCodes.Status401Unauthorized;
                   op.Response.WriteAsync(JsonConvert.SerializeObject(new
                   {
                       status = StatusCodes.Status401Unauthorized,
                       msg = "token無效"
                   }));
                   return Task.CompletedTask;
               }
           };
       });

三、添加認證中間件

//這裏注意 必定要在 UseMvc前面,順序不可改變
app.UseAuthentication();

四、Controller 添加特性認證 [Authorize]

    [Route("api/[controller]")]
    [Authorize] 
    public class SuiBianController : Controller
    {
        [HttpGet]
        public string Get()
        {
            var roles = User.Claims.Where(l => l.Type == ClaimTypes.Role);
            return "訪問成功,當前用戶角色 " + string.Join(',', roles.Select(l => l.Value));
        }
    }

五、測試

  訪問 http://localhost:5001/api/suibian ,提示 token 無效,證實咱們增長認證成功

  

4、客戶端測試

一、修改 IdpController, 添加一個action 訪問 API資源 /api/suibian

    public class IdpController : Controller
    {
        //內存緩存 須要提早註冊  services.AddMemoryCache();
        private IMemoryCache _memoryCache;
        private static readonly string _idpBaseUrl = "http://localhost:5002";
        private static readonly string _apiBaseUrl = "http://localhost:5001";
        public IdpController(IMemoryCache memoryCache)
        {
            _memoryCache = memoryCache;
        }
        public async Task<IActionResult> Token()
        {
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync(_idpBaseUrl);
            if (disco.IsError)
            {
                return Content("獲取發現文檔失敗。error:" + disco.Error);
            }
            var token = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest()
            {
                Address = disco.TokenEndpoint,
                ClientId = "client",
                ClientSecret = "secret",
                Scope = "api1"
            });
            if (token.IsError)
            {
                return Content("獲取 AccessToken 失敗。error:" + disco.Error);
            }
            //將token 臨時存儲到 緩存中
            _memoryCache.Set("AccessToken", token.AccessToken);
            return Content("獲取 AccessToken 成功。Token:" + token.AccessToken);
        }

        public async Task<IActionResult> SuiBian()
        {
            string token, apiurl = GetApiUrl("suibian");
            _memoryCache.TryGetValue("AccessToken", out token);
            if (string.IsNullOrEmpty(token))
            {
                return Content("token is null");
            }
            var client = new HttpClient();
            client.SetBearerToken(token);
            var response = await client.GetAsync(apiurl);
            var result = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                _memoryCache.Remove("AccessToken");
                return Content($"獲取 {apiurl} 失敗。StatusCode:{response.StatusCode} \r\n Token:{token} \r\n result:{result}");
            }
            return Json(new
            {
                code = response.StatusCode,
                data = result
            });
        }

        private string GetApiUrl(string address)
        {
            return _apiBaseUrl + "/api/" + address;
        }
    }

二、請求 AccessToken

  http://localhost:5003/Idp/token ,請求成功後會將 token 存儲到 cache 中

     

三、請求 API 資源

  http://localhost:5003/Idp/suibian ,token是直接在緩存裏面取出來的

5、項目目錄

 

相關文章
相關標籤/搜索