• 认证授权:IdentityServer4


    前言

     上一篇文章介绍了IdentityServer4的各种授权模式,本篇继续介绍使用IdentityServer4实现单点登录效果。

    单点登录(SSO)

     SSO( Single Sign-On ),中文意即单点登录,单点登录是一种控制多个相关但彼此独立的系统的访问权限,拥有这一权限的用户可以使用单一的ID和密码访问某个或多个系统从而避免使用不同的用户名或密码,或者通过某种配置无缝地登录每个系统。

     概括就是:一次登录,多处访问

    案例场景:

     1、提供资源服务(WebApi):订单:Order(cz.Api.Order)、商品:Goods(cz.Api.Goods)……

     2、业务中存在多个系统:门户系统、订单系统、商品系统……

     3、实现用户登录门户后,跳转订单系统、商品系统时,不需要登录认证(单点登录效果)

    一、环境准备:

     调整项目如下图结构:

     

      在身份认证项目(cz.IdentityServer)中InMemoryConfig中客户端列表中添加以下客户端内容:(其他内容同上一篇设置相同)

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

    二、程序实现:

     1、订单、商品Api项目:

      a)订单API项目调整:添加Nuget包引用:

    Install-Package IdentityServer4.AccessTokenValidation

      b)调整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.AddControllers();
    
            //IdentityServer
            services.AddMvcCore()
                    .AddAuthorization();
    
            //配置IdentityServer
            services.AddAuthentication("Bearer")
                    .AddIdentityServerAuthentication(options =>
                    {
                        options.RequireHttpsMetadata = false; //是否需要https
                        options.Authority = $"http://localhost:5600";  //IdentityServer授权路径
                        options.ApiName = "order";  //需要授权的服务名称
                    });
        }
    
        // 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.UseAuthentication();
    
            app.UseAuthorization();
    
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

      c)添加控制器:OrderController 

    namespace cz.Api.Order.Controllers
    {
       [ApiController]
       [Route("[controller]")]
       [Authorize]    
      public class OrderController : ControllerBase { private static readonly string[] Summaries = new[] { "Order1", "Order2", "Order3", "Order4", "Order5", "Order6", "Order7", "Order8", "Order9", "Order10" }; private readonly ILogger<OrderController> _logger; public OrderController(ILogger<OrderController> logger) { _logger = logger; }      //模拟返回数据 [HttpGet] public IEnumerable<WeatherForecast> Get() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } } }

      d)商品项目同步调整,调整Api和方法

     2、门户项目:

      添加Nuget引用:

    Install-Package IdentityServer4.AccessTokenValidation
    Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect

      a)调整HomeController如下内容:

    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
    
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }
        [Authorize]
        public IActionResult Index()
        {
            //模拟返回应用列表
            List<AppModel> apps = new List<AppModel>();
            apps.Add(new AppModel() { AppName = "Order Client", Url = "http://localhost:5021" });
            apps.Add(new AppModel() { AppName = "Goods Client", Url = "http://localhost:5022" });
            return View(apps);
        }
    
        [Authorize]
        public IActionResult Privacy()
        {
            return View();
        }
    public IActionResult Logout()
        {
            return SignOut("oidc", "Cookies");
        }
    
    }

      b)调整主页视图: 

    @model List<AppModel>
    @{
        ViewData["Title"] = "Home Page";
    }
    <style>
        .box-wrap {
            text-align: center;
            /*        background-color: #d4d4f5;*/
            overflow: hidden;
        }
    
            .box-wrap > div {
                 31%;
                padding-bottom: 31%;
                margin: 1%;
                border-radius: 10%;
                float: left;
                background-color: #36A1DB;
            }
    </style>
    <div class="text-center">
        <div class="box-wrap">
            @foreach (var item in Model)
            {
                <div class="box">
                    <a href="@item.Url" target="_blank">@item.AppName</a>
                </div>
            }
        </div>
    </div>

      c)调整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 = "main_client";
            options.ClientSecret = "secret";
            options.ResponseType = "id_token";
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;
            //事件
            options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents()
            {
                //远程故障
                OnRemoteFailure = context =>
                {
                    context.Response.Redirect("/");
                    context.HandleResponse();
                    return Task.FromResult(0);
                },
                //访问拒绝
                OnAccessDenied = context =>
                {
                    //重定向到指定页面
                    context.Response.Redirect("/");
                    //停止此请求的所有处理并返回给客户端
                    context.HandleResponse();
                    return Task.FromResult(0);
                },
            };
        });
    }

     3、订单、商品客户端项目:

      添加Nuget引用:

    Install-Package IdentityServer4.AccessTokenValidation
    Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect

      a)修改HomeController内容如下:

    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
    
        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }
    
        [Authorize]
        public IActionResult Index()
        {
            return View();
        }
    
        public async Task<IActionResult> PrivacyAsync()
        {
            var accessToken = await HttpContext.GetTokenAsync("access_token");
            var client = new HttpClient();
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var content = await client.GetStringAsync("http://localhost:5601/order");
    
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
            var contentgoods = await client.GetStringAsync("http://localhost:5602/goods");
    
            ViewData["Json"] = $"Goods:{contentgoods}
     " +
                $"Orders:{content}";
            return View();
        }
    
        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
        public IActionResult Logout()
        {
            return SignOut("oidc", "Cookies");
        }
    }

      b)调整对应视图内容:

    
    
    #####Home.cshtml
    @{
        ViewData["Title"] = "Home Page";
    }
    @using Microsoft.AspNetCore.Authentication
    
    <h2>Claims</h2>
    <div class="text-center">
        <dl>
            @foreach (var claim in User.Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>
    </div>
    <div class="text-center">
        <h2>Properties</h2>
        <dl>
            @foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
            {
                <dt>@prop.Key</dt>
                <dd>@prop.Value</dd>
            }
        </dl>
    </div>

    #####Privacy.cshtml

    @{
    ViewData["Title"] = "API Result";
    }
    <h1>@ViewData["Title"]</h1>

    <p>@ViewData["Json"]</p>

      c)Statup中设置客户端信息  

    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 = "order_client";
            options.ClientSecret = "secret";
            options.ResponseType = "code";
            options.SaveTokens = true;
            options.Scope.Add("order");
            options.Scope.Add("goods");
            options.GetClaimsFromUserInfoEndpoint = true;
            //事件
            options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents()
            {
                //远程故障
                OnRemoteFailure = context =>
                {
                    context.Response.Redirect("/");
                    context.HandleResponse();
                    return Task.FromResult(0);
                },
                //访问拒绝
                OnAccessDenied = context =>
                {
                    //重定向到指定页面
                    context.Response.Redirect("/");
                    //停止此请求的所有处理并返回给客户端
                    context.HandleResponse();
                    return Task.FromResult(0);
                },
            };
        });
    }

     d)商品客户端调整按照以上内容调整类似。

    三、演示效果:

       1、设置项目启动如下图:

        2、示例效果:

       

    四、总结:

      通过以上操作,整理单点登录流程如下图:

        

      踩坑:当登录取消、授权提示拒绝时,总是跳转错误界面。

        解决办法:客户端定义时,定义事件:对访问拒绝添加处理逻辑。

    //事件
    options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents()
    {
      //远程故障
      OnRemoteFailure = context =>
      {
        context.Response.Redirect("/");
        context.HandleResponse();
        return Task.FromResult(0);
      },
      //访问拒绝
      OnAccessDenied = context =>
      {
        //重定向到指定页面
        context.Response.Redirect("/");
        //停止此请求的所有处理并返回给客户端
        context.HandleResponse();
        return Task.FromResult(0);
      },
    };

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

  • 相关阅读:
    如何下载Bilibili视频
    pyqt5 主界面打开新主界面、打开Dialog、打开提示框的实现模板
    【爬坑】python3+pyqt5+pyinstaller 打包成exe的各种问题
    【PyQt5-Qt Designer】QComboBox(下拉列表框) 使用模板
    【PyQt5-Qt Designer】读取txt文件在打印
    pyqt5核心-信号与槽(第二弹)
    使用QT设计师-信号和槽signal-slot(第一弹)
    【python基础】python程序打包成.exe运行时会弹出黑框
    【pyqt5】QdateTimeEdit(日期时间)
    pyqt5-对文本样式进行操作
  • 原文地址:https://www.cnblogs.com/cwsheng/p/13663789.html
Copyright © 2020-2023  润新知