IdentityServer4 (1) 客户端授权模式

  • A+
所属分类:.NET技术
摘要

  git地址:https://github.com/yizhaoxian/CoreIdentityServer4Demo.git  2.1、《IdentityServer4 (1) 客户端授权模式》
  2.2、《IdentityServer4 (2) 密码授权》
  2.3、《IdentityServer4 (3) 授权码模式(Authorization Code)》
  2.4、《IdentityServer4 (4) 静默刷新(Implicit)》
  2.5、《IdentityServer4 (5) 混合模式(Hybrid)》


写在前面

1、源码(.Net Core 2.2)

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

2、相关章节

  2.1、《IdentityServer4 (1) 客户端授权模式
  2.2、《IdentityServer4 (2) 密码授权
  2.3、《IdentityServer4 (3) 授权码模式(Authorization Code)》
  2.4、《IdentityServer4 (4) 静默刷新(Implicit)》
  2.5、《IdentityServer4 (5) 混合模式(Hybrid)》

3、参考资料

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

4、流程图

  客户端授权模式是最基本的使用场景,我们需要做一个API(受保护的资源),一个客户端(访问的应用),一个IdentityServer(用来授权)

  IdentityServer4 (1) 客户端授权模式

一、创建IdentityServer

1、用VS创建一个Web 项目

  IdentityServer4 (1) 客户端授权模式

2、添加引用 IdentityServer4 包,下图是我已经安装好了的截图

IdentityServer4 (1) 客户端授权模式

3、添加一个配置文件(这里也可以使用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" }                 }             };         }     }

4、在StartUp.cs 里注册 IdentityServer4 

  ConfigureServices()

    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();

5、配置完成

  启动项目,访问 http://localhost:5002/.well-known/openid-configuration (我的端口号是5002) ,可以浏览 发现文档,参考下图,说明已经配置成功。

  后面客户端会使用里面的数据进行请求toke

  项目第一次启动根目录也会生成一个文件 tempkey.rsa

   IdentityServer4 (1) 客户端授权模式

二、客户端

1、新建一个.Net Core Web 项目

  这里可以使用其他建立客户端 。例如:控制台程序、wpf 等等。需要添加 NuGet 包 IdentityModel

  IdentityServer4 (1) 客户端授权模式

2、新建一个 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);         }       }

IdentityServer4 (1) 客户端授权模式

三、添加API资源

1、新建一个API项目

  我把API项目和IdentityServer 放到同一个解决方案,这个自己定,无所谓的

  API资源指的是IdentityServer IdpConfig.GetApis() 里面添加的 api1(这个api1名称随便起,但是要注意一定要保持一致)

  添加认证之后就可以测试用 AccessToken 请求资源了

2、添加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;                }            };        });

3、添加认证中间件

//这里注意 一定要在 UseMvc前面,顺序不可改变 app.UseAuthentication();

4、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));         }     }

5、测试

  访问 http://localhost:5001/api/suibian ,提示 token 无效,证明我们增加认证成功

  IdentityServer4 (1) 客户端授权模式

四、客户端测试

1、修改 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} rn Token:{token} rn result:{result}");             }             return Json(new             {                 code = response.StatusCode,                 data = result             });         }          private string GetApiUrl(string address)         {             return _apiBaseUrl + "/api/" + address;         }     }

2、请求 AccessToken

  http://localhost:5003/Idp/token ,请求成功后会将 token 存储到 cache 中

     IdentityServer4 (1) 客户端授权模式

3、请求 API 资源

  http://localhost:5003/Idp/suibian ,token是直接在缓存里面取出来的

IdentityServer4 (1) 客户端授权模式

五、项目目录

 IdentityServer4 (1) 客户端授权模式