• 认证授权:IdentityServer4


    前言:

     前面介绍了IdentityServer4 的简单应用,本篇将继续讲解IdentityServer4 的各种授权模式使用示例

    授权模式:

     环境准备

     a)调整项目结构如下:

      

     b)调整cz.IdentityServer项目中Statup文件如下 

    public class Startup
      {
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddControllersWithViews();
    
                services.Configure<CookiePolicyOptions>(options =>
                {
                    options.MinimumSameSitePolicy = SameSiteMode.Strict;
                });
    
                services.AddIdentityServer()
                  .AddDeveloperSigningCredential()
                  //api资源
                  .AddInMemoryApiResources(InMemoryConfig.GetApiResources())
                  //4.0版本需要添加,不然调用时提示invalid_scope错误
                  .AddInMemoryApiScopes(InMemoryConfig.GetApiScopes())
                  .AddTestUsers(InMemoryConfig.Users().ToList())
                  .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResourceResources())
                  .AddInMemoryClients(InMemoryConfig.GetClients());
            }
    
            // 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.UseRouting();
    
                app.UseStaticFiles();
                app.UseCookiePolicy();
                app.UseIdentityServer();
    
                app.UseAuthentication();
                //使用默认UI,必须添加
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
                });
      }
    }

     c)在cz.Api.Order项目中添加控制器:IdentityController

    namespace cz.Api.Order.Controllers
    {
        [Route("identity")]
        [ApiController]
        [Authorize]
        public class IdentityController : ControllerBase
        {
            [HttpGet]
            public IActionResult Get()
            {
                return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
            }
        }
    }

     1、客户端模式

      a)在InMemoryConfigGetClients方法中添加客户端:

    new Client
    {
        ClientId = "credentials_client", //访问客户端Id,必须唯一
        ClientName = "ClientCredentials Client",
        //使用客户端授权模式,客户端只需要clientid和secrets就可以访问对应的api资源。
        AllowedGrantTypes = GrantTypes.ClientCredentials,
        ClientSecrets =
            {
                new Secret("secret".Sha256())
            },
        AllowedScopes = {
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile,
            "goods"
        },
    },     

      b)在cz.ConsoleClient项目中安装Nuget包:IdentityModel,在Program中添加如下方法:

    /// <summary>
    /// 客户端认证模式
    /// </summary>
    private static void ClientCredentials_Test()
    {
        Console.WriteLine("ClientCredentials_Test------------------->");
        var client = new HttpClient();
        var disco = client.GetDiscoveryDocumentAsync("http://localhost:5600/").Result;
        if (disco.IsError)
        {
            Console.WriteLine(disco.Error);
            return;
        }
        //请求token
        var tokenResponse = client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = "credentials_client",
            ClientSecret = "secret",
            Scope = "goods"
        }).Result;
    
        if (tokenResponse.IsError)
        {
            Console.WriteLine(tokenResponse.Error);
            return;
        }
    
        Console.WriteLine(tokenResponse.Json);
        //调用认证api
        var apiClient = new HttpClient();
        apiClient.SetBearerToken(tokenResponse.AccessToken);
    
        var response = apiClient.GetAsync("http://localhost:5601/identity").Result;
        if (!response.IsSuccessStatusCode)
        {
            Console.WriteLine(response.StatusCode);
        }
        else
        {
            var content = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine(content);
        }
    }

       运行该程序结果如下:

       

     2、密码模式

      a)在InMemoryConfigGetClients方法中添加客户端:

    new Client
    {
        ClientId = "password_client",
        ClientName = "Password Client",
        ClientSecrets = new [] { new Secret("secret".Sha256()) },
        //这里使用的是通过用户名密码换取token的方式.
        AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
        AllowedScopes = {
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile,
            "order","goods",
        }
    },

      b)cz.ConsoleClient项目继续在Program中添加如下方法:

    /// <summary>
    /// 用户名密码模式
    /// </summary>
    public static void ResourceOwnerPassword_Test()
    {
        Console.WriteLine("ResourceOwnerPassword_Test------------------->");
        // request token
        var client = new HttpClient();
        var disco = client.GetDiscoveryDocumentAsync("http://localhost:5600/").Result;
        var tokenResponse = client.RequestPasswordTokenAsync(new PasswordTokenRequest()
        {
            Address = disco.TokenEndpoint,
            ClientId = "password_client",
            ClientSecret = "secret",
            UserName = "cba",
            Password = "cba",
            Scope = "order goods",
        }).Result;
    
        if (tokenResponse.IsError)
        {
            Console.WriteLine(tokenResponse.Error);
            return;
        }
        Console.WriteLine(tokenResponse.Json);
        // call api
        var apiClient = new HttpClient();
        client.SetBearerToken(tokenResponse.AccessToken);
        var response = apiClient.GetAsync("http://localhost:5601/identity").Result;
        if (!response.IsSuccessStatusCode)
        {
            Console.WriteLine(response.StatusCode);
        }
        else
        {
            var content = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine(content);
        }
    }

       运行该程序结果同上:  

     3、简化模式

      a)在InMemoryConfigGetClients方法中添加客户端:

    new Client
    {
        ClientId = "implicit_client",
        ClientName = "Implicit Client",
        ClientSecrets = new [] { new Secret("secret".Sha256()) },
        AllowedGrantTypes = GrantTypes.Implicit,
        AllowedScopes = {
            "order","goods",
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile
        },
        RedirectUris = { "http://localhost:5021/signin-oidc" },
        PostLogoutRedirectUris = { "http://localhost:5021" },
        //是否显示授权提示界面
        RequireConsent = true,
    },

      b)调整在cz.MVCClient中Statup文件中内容如下:

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
    
        public IConfiguration Configuration { get; }
    
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
              // This lambda determines whether user consent for non-essential cookies is needed for a given request.
              options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.Lax;
            });
    
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
    
            services.AddControllersWithViews();
    
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.RequireHttpsMetadata = false;
                options.Authority = "http://localhost:5600";
                options.ClientId = "implicit_client";
                options.ClientSecret = "secret";
            });
        }
    
        // 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();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();
            app.UseCookiePolicy();
    
            app.UseRouting();
    
            app.UseAuthentication();
            app.UseAuthorization();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

      c)在cz.MVCClient中添加Nuget包:IdentityServer4.AccessTokenValidation、Microsoft.AspNetCore.Authentication.OpenIdConnect;在HomeController中添加方法:

    [Authorize]
    public IActionResult Secure()
    {
        ViewData["Message"] = "Secure page.";
    
        return View();
    }
    //注销
    public IActionResult Logout()
    {
        return SignOut("oidc", "Cookies");
    }

      d)界面调整:

       在_Layout.cshtml文件中添加导航按钮:Secure、Logout   

    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Secure">Secure</a>
    </li>
    @if (User.Identity.IsAuthenticated)
    {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Logout">Logout</a>
        </li>
    }

       添加视图:Secure.cshtml文件:

    @{
        ViewData["Title"] = "Secure";
    }
    
    <h2>@ViewData["Title"]</h2>
    
    <h3>User claims</h3>
    
    <dl>
        @foreach (var claim in User.Claims)
        {
            <dt>@claim.Type</dt>
            <dd>@claim.Value</dd>
        }
    </dl>

      e)运行结果如下:

      

      简化模式还支持在Js客户端中运行可以查看官方说明文档:https://identityserver4.readthedocs.io/en/latest/quickstarts/4_javascript_client.html

     4、授权码模式

      a)在InMemoryConfigGetClients方法中添加客户端:

    new Client
    {
        ClientId = "code_client",
        ClientName = "Code Client",
        ClientSecrets = new [] { new Secret("secret".Sha256()) },
        AllowedGrantTypes = GrantTypes.Code,
        RedirectUris = { "http://localhost:5021/signin-oidc" },
        PostLogoutRedirectUris = { "http://localhost:5021/signout-callback-oidc" },
      //是否显示授权提示界面 RequireConsent
    = true, AllowedScopes = { "order","goods", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } },

      b)调整在cz.MVCClient中Statup文件中ConfigureServices方法内容如下:

    // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
    
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.Lax;
            });
    
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
    
            services.AddControllersWithViews();
    
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.RequireHttpsMetadata = false;
                options.Authority = "http://localhost:5600";
                options.ClientId = "code_client";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.SaveTokens = true;
                options.Scope.Add("order");
                options.Scope.Add("goods");
                options.GetClaimsFromUserInfoEndpoint = true;
            });
        }

      c)运行结果如下:同简化模式运行效果相同

     5、混合模式(Hybrid)

    a)在InMemoryConfigGetClients方法中添加客户端:

    new Client
    {
        ClientId = "hybrid_client",
        ClientName = "Hybrid Client",
        ClientSecrets = new [] { new Secret("secret".Sha256()) },
        AllowedGrantTypes = GrantTypes.Hybrid,
        //是否显示授权提示界面
        RequireConsent = true,
        AllowedScopes = {
            "order","goods",
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile
        }
    }

      b)调整在cz.MVCClient中Statup文件中ConfigureServices方法内容如下:

    public void ConfigureServices(IServiceCollection services)
    {
    
        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.Lax;
        });
    
        JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
    
        services.AddControllersWithViews();
    
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.RequireHttpsMetadata = false;
            options.Authority = "http://localhost:5600";
            options.ClientId = "hybrid_client";
            options.ClientSecret = "secret";
            options.ResponseType = "code token id_token";
            options.SaveTokens = true;
            options.ResponseMode = "fragment";
            options.GetClaimsFromUserInfoEndpoint = true;
            options.Scope.Add("order");
            options.Scope.Add("goods");
        });
    }

    总结:

     应用场景总结

    • 客户端模式(Client Credentials):和用户无关,应用于应用程序与 API 资源之间的交互场景。
    • 密码模式:和用户有关,常用于第三方登录。
    • 简化模式:可用于前端或无线端。
    • 混合模式:推荐使用,包含 OpenID 认证服务和 OAuth 授权,针对的是后端服务调用。

      过程中遇到的坑:

    • Postman调用时总是提示:invalid_scope异常;

       解决:在添加IdentityServer服务时:调用AddInMemoryApiScopes方法注册Scope

    • MVC项目登录成功后跳转时,找不到http://localhost:5020/signin-oidc路径:

       解决:在Statup文件中添加services.Configure<CookiePolicyOptions>(options =>{options.CheckConsentNeeded = context => true;options.MinimumSameSitePolicy = SameSiteMode.Lax; });

    • 登录时授权界面展示展示:

       解决:客户端注册时,指定属性RequireConsent= true

    Git地址:https://github.com/cwsheng/IdentityServer.Demo.git

  • 相关阅读:
    postman 调试接口报“401 身份认证信息未提供”错误
    UserWarning: XXX is writable by group/others and vulnerable to attack when used with get_resource_filename.
    以root权限执行python时候脚本时候报错“ExtractionError: Can't extract file(s) to egg cache”
    django接口调试示例说明
    查看linux系统版本、内存、CPU、存储容量
    一次批量杀死多个进程
    bash:pybot未找到命令
    Swoft-Api项目部署九:前、后置中间件
    Swoft-Api项目部署八:主从数据库配置
    Swoft-Api项目部署七:验证器
  • 原文地址:https://www.cnblogs.com/cwsheng/p/13662414.html
Copyright © 2020-2023  润新知