Asp.Net MVC接入Identity Server4 全记录

Asp.Net MVC接入Identity Server4 全记录

当前环境

  1. Net Core 2.2+ //建议使用Net Core 3.0

  2. Asp.Net Framework 4.6.2+

  3. Visual Studio 2019//如果使用Net Core 3.0,你可能需要预览版

新增空的解决方案

  1. 打开命令行。执行dotnet new sln -n SsoTest 建立空白解决方案。

新增Identity Server4 服务端【本文不讨论服务端配置问题】

新增项目并添加到解决方案里

  1. 打开命令行或者powershell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 新增IdentityServer4模板
    dotnet new -i IdentityServer4.Templates
    # 新建项目
    dotnet new is4empty -n IdentityServer
    # 添加到解决方案
    dotnet sln add .\IdentityServer\IdentityServer.csproj
    # 进入项目目录
    cd IdentityServer
    # 添加UI
    dotnet new is4ui
  2. 修改Config.cs文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    using IdentityServer4;
    using IdentityServer4.Models;
    using IdentityServer4.Test;
    using System.Collections.Generic;
    using System.Security.Claims;

    namespace IdentityServer
    {
    public static class Config
    {
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
    return new IdentityResource[]
    {
    new IdentityResources.OpenId(),
    new IdentityResources.Profile(),
    new IdentityResources.Email(),
    new IdentityResources.Phone(),
    };
    }
    public static List<TestUser> GetUsers()
    {
    return new List<TestUser> {
    new TestUser {
    SubjectId = "1",
    Username = "alice",
    Password = "password",
    Claims = new []
    {
    new Claim("name", "Alice"),
    new Claim("website", "https://alice.com")
    }
    },
    new TestUser {
    SubjectId = "2",
    Username = "bob",
    Password = "password",
    Claims = new []
    {
    new Claim("name", "Bob"),
    new Claim("website", "https://bob.com")
    }
    }
    };
    }
    public static IEnumerable<ApiResource> GetApis()
    {
    return new ApiResource[] {
    new ApiResource ("api1", "My API")
    };
    }

    public static IEnumerable<Client> GetClients()
    {
    var secret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256();
    return new Client[] {
    new Client {
    ClientId = "mvc",
    ClientName = "MVC Client",
    ClientSecrets = {
    new Secret (secret)
    },
    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
    // where to redirect to after login
    RedirectUris = { "http://localhost:5001/signin-oidc" },
    // where to redirect to after logout
    PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" },
    AllowedScopes = new List<string> {
    IdentityServerConstants.StandardScopes.OpenId,
    IdentityServerConstants.StandardScopes.Email,
    IdentityServerConstants.StandardScopes.Phone,
    IdentityServerConstants.StandardScopes.Profile,
    "api1"
    }
    }};
    }
    }
    }
  3. 修改Startup.cs,取消注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    public IHostingEnvironment Environment { get; }

    public Startup(IHostingEnvironment environment)
    {
    Environment = environment;
    }

    public void ConfigureServices(IServiceCollection services)
    {
    // uncomment, if you want to add an MVC-based UI
    services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);

    var builder = services.AddIdentityServer()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApis())
    .AddInMemoryClients(Config.GetClients())
    .AddTestUsers(Config.GetUsers());

    if (Environment.IsDevelopment())
    {
    builder.AddDeveloperSigningCredential();
    }
    else
    {
    throw new Exception("need to configure key material");
    }
    }

    public void Configure(IApplicationBuilder app)
    {
    if (Environment.IsDevelopment())
    {
    app.UseDeveloperExceptionPage();
    }

    // uncomment if you want to support static files
    app.UseStaticFiles();

    app.UseIdentityServer();
    // uncomment, if you want to add an MVC-based UI
    app.UseMvcWithDefaultRoute();
    }
  4. 新开一个命令行或者powershell窗口,运行服务端

    1
    2
    3
    4
    5
    6

    # 还原nuget,编译
    dotnet restore
    dotnet build
    # 运行
    dotnet run

新增一个空的MVC5项目

  1. 打开解决方案SsoTest.sln
  2. 在解决方案上右键->添加->新建项目,创建MVC5项目,名为SSOTest.Client



配置MVC5接入Identity Server

  1. 修改Client项目属性,指定web端口为5001

  2. 打开包控制台,安装nuget包

    1
    2
    3
    4
    Install-Package IdentityModel -Version 3.10.10
    Install-Package Microsoft.Owin.Security.Cookies
    Install-Package Microsoft.Owin.Security.OpenIdConnect
    Install-Package Microsoft.Owin.Host.SystemWeb
  3. 新增OWIN的Startup.cs文件

  4. 修改为Startup.cs文件.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    using System;
    using System.Net.Http;
    using System.Security.Claims;
    using System.Web.Helpers;
    using IdentityModel;
    using IdentityModel.Client;
    using Microsoft.Owin;
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.Cookies;
    using Microsoft.Owin.Security.OpenIdConnect;
    using Owin;

    [assembly: OwinStartup(typeof(SSOTest.Client.Startup))]

    namespace SSOTest.Client
    {
    public partial class Startup
    {
    public void Configuration(IAppBuilder app)
    {
    ConfigureAuth(app);
    }

    public void ConfigureAuth(IAppBuilder app)
    {
    AntiForgeryConfig.UniqueClaimTypeIdentifier = "sub";

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
    AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
    CookieHttpOnly = true
    });
    var secret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".ToSha256();
    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
    Authority = "http://localhost:5000",
    ClientId = "mvc",
    ClientSecret = secret,
    RedirectUri = "http://localhost:5001/signin-oidc",
    PostLogoutRedirectUri = "http://localhost:5001/signout-oidc",
    ResponseType = "code id_token",
    Scope = "openid profile",
    RequireHttpsMetadata = false,
    SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
    Notifications = new OpenIdConnectAuthenticationNotifications
    {
    AuthorizationCodeReceived = async n =>
    {
    var client = new HttpClient();
    var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest()
    {
    Address = "http://localhost:5000",
    Policy ={
    RequireHttps = false
    }
    });
    if (disco.IsError) throw new Exception(disco.Error);
    // use the code to get the access and refresh token
    var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest()
    {
    Address = disco.TokenEndpoint,
    ClientId = "mvc",
    ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0",
    Code = n.Code,
    RedirectUri = n.RedirectUri
    });

    if (tokenResponse.IsError)
    {
    throw new Exception(tokenResponse.Error);
    }
    // use the access token to retrieve claims from userinfo
    var userInfoResponse = await client.GetUserInfoAsync(new UserInfoRequest()
    {
    Address = disco.UserInfoEndpoint,
    Token = tokenResponse.AccessToken
    });

    // create new identity
    var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
    id.AddClaims(userInfoResponse.Claims);
    id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
    id.AddClaim(new Claim("expires_at", DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn).ToString()));
    //id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
    id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
    id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

    n.AuthenticationTicket = new AuthenticationTicket(
    new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
    n.AuthenticationTicket.Properties);
    //TODO 本地USER同步
    foreach (var item in userInfoResponse.Claims)
    {
    if (item.Type == "sub")
    {
    }
    }
    }
    }
    });
    }
    }
    }
  5. 打开Controllers/HomeController.cs
    About这个Action上加特性[Authorize]

  6. 运行Client项目,访问 http://localhost:5001/Home/About

源码下载

on the github