zoukankan      html  css  js  c++  java
  • 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} 
     Token:{token} 
     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是直接在缓存里面取出来的

    五、项目目录

     

  • 相关阅读:
    7.9 C++ STL算法
    7.8 C++容器适配器
    7.7 C++基本关联式容器
    Django项目静态文件加载失败问题
    Centos6.5安装mysql5.7详解
    使用Xshell上传下载文件
    linux中MySQL本地可以连接,远程连接不上问题
    Linux常用命令
    Linux环境安装python3
    python 字符串最长公共前缀
  • 原文地址:https://www.cnblogs.com/Zing/p/13361386.html
Copyright © 2011-2022 走看看