認證授權: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 {
            width: 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}\r\n " + $"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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM