19

认证授权:IdentityServer4 - 单点登录

 3 years ago
source link: http://www.cnblogs.com/cwsheng/p/13663789.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

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

单点登录(SSO)

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

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

案例场景:

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

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

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

一、环境准备:

调整项目如下图结构:

a2Abqmu.png!mobile

在身份认证项目(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、设置项目启动如下图:

mmI7rme.png!mobile

2、示例效果:

qeiuqaJ.gif!mobile

四、总结:

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

2qiqiyB.png!mobile

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

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

//事件
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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK