IdentityServer4 (1) 客户端授权模式(Client Credentials)

写在前面

1、源码(.Net Core 2.2)

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

2、相关章节

  2.1、《IdentityServer4 (1) 客户端授权模式(Client Credentials)
  2.2、《IdentityServer4 (2) 密码授权(Resource Owner Password)
  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(用来授权)

  

一、创建IdentityServer

1、用VS创建一个Web 项目

  

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

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

  

二、客户端

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

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

  

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

三、添加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 无效,证明我们增加认证成功

  

四、客户端测试

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} \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;        }    }

2、请求 AccessToken

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

3、请求 API 资源

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

五、项目目录

(0)

相关推荐