IdentityServer4 實現 OpenID Connect 和 OAuth 2.0

435188-20170110174201760-808889030.png

關於 OAuth 2.0 的相關內容,點擊查看:ASP.NET WebApi OWIN 實現 OAuth 2.0html

OpenID 是一個去中心化的網上身份認證系統。對於支持 OpenID 的網站,用戶不須要記住像用戶名和密碼這樣的傳統驗證標記。取而代之的是,他們只須要預先在一個做爲 OpenID 身份提供者(identity provider, IdP)的網站上註冊。OpenID 是去中心化的,任何網站均可以使用 OpenID 來做爲用戶登陸的一種方式,任何網站也均可以做爲 OpenID 身份提供者。OpenID 既解決了問題而又不須要依賴於中心性的網站來確認數字身份。前端

OpenID 相關基本術語:git

  • 最終用戶(End User):想要向某個網站代表身份的人。github

  • 標識(Identifier):最終用戶用以標識其身份的 URL 或 XRI。web

  • 身份提供者(Identity Provider, IdP):提供 OpenID URL 或 XRI 註冊和驗證服務的服務提供者。sql

  • 依賴方(Relying Party, RP):想要對最終用戶的標識進行驗證的網站。數據庫


以上概念來自:https://zh.wikipedia.org/wiki/OpenIDjson

針對 .NET Core 跨平臺,微軟官方並無針對 OAuth 2.0 的實現(Microsoft.AspNetCore.Authentication.OAuth組件,僅限客戶端),IdentityServer4 實現了 ASP.NET Core 下的 OpenID Connect 和 OAuth 2.0,IdentityServer4 也是微軟基金會成員。後端

閱讀目錄:api

  • OpenID 和 OAuth 的區別

  • 客戶端模式(Client Credentials)

  • 密碼模式(resource owner password credentials)

  • 簡化模式-With OpenID(implicit grant type)

  • 簡化模式-With OpenID & OAuth(JS 客戶端調用)

  • 混合模式-With OpenID & OAuth(Hybrid Flow)

  • ASP.NET Core Identity and Using EntityFramework Core for configuration data

開源地址:https://github.com/yuezhongxin/IdentityServer4.Demo

1. OpenID 和 OAuth 的區別

簡單歸納:

  • OpenID:authentication(認證),用戶是誰?

  • OAuth:authorization(受權),用戶能作什麼?

其實,OAuth 的密碼受權模式和 OpenID 有些相似,但也不相同,好比用戶登陸落網選擇微博快捷登陸方式,大體的區別:

  • OAuth:用戶在微博受權頁面輸入微博的帳號和密碼,微博驗證成功以後,返回 access_token,而後落網拿到 access_token 以後,再去請求微博的用戶 API,微博受權中心驗證 access_token,若是驗證經過,則返回用戶 API 的請求數據給落網。

  • OpenID:落網能夠沒有用戶的任何實現,落網須要確認一個 URL 標識(能夠是多個),而後用戶登陸的時候,選擇一個 URL 進行登陸(好比微博),跳轉到微博 OpenID 登陸頁面,用戶輸入微博的帳號和密碼,微博驗證成功以後,按照用戶的選擇,返回用戶的一些信息。

能夠看到,OAuth 首先須要拿到一個受權(access_token),而後再經過這個受權,去資源服務器(具體的 API),獲取想要的一些數據,上面示例中,用戶 API 只是資源服務器的一種(能夠是視頻 API、文章 API 等等),在這個過程當中,OAuth 最重要的就是獲取受權(四種模式),獲取到受權以後,你就能夠經過這個受權,作受權範圍之類的任何事了。

而對於 OpenID 來講,受權和它沒任何關係,它只關心的是用戶,好比落網,能夠不進行用戶的任何實現(具體體現就是數據庫沒有 User 表),而後使用支持 OpenID 的服務(好比微博),經過特定的 URL 標識(能夠看做是 OpenID 標識),而後輸入提供服務的帳號和密碼,返回具體的用戶信息,對於落網來講,它關心的是用戶信息,僅此而已。

435188-20170111105252166-1157332397.png

上面實際上是 OAuth 的受權,因此會有「得到如下權限」提示,若是是 OpenID 的話,「權限」應該改成「用戶信息」。

支持 OpenID 的服務列表:http://openid.net/get-an-openid/

OpenID 流程圖(來自 Using OpenID):

435188-20170111120202150-598990342.gif

2. 客戶端模式(Client Credentials)

簡單概述:客戶端提供 ClientId 和 ClientSecret 給認證受權服務,驗證若是成功,返回 access_token,客戶端拿到 access_token,訪問 API 資源服務。

2.1 認證受權服務配置

建立 ASP.NET Core 站點,Startup 配置修改以下:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryApiResources(new List<ApiResource>
            {
                new ApiResource("api1", "My API")
            })
            .AddInMemoryClients(new List<Client>
            {
                // client credentials client
                new Client
                {
                    ClientId = "client",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,

                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = { "api1" }
                }
            });
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(LogLevel.Debug);
        app.UseDeveloperExceptionPage();

        app.UseIdentityServer();
    }
}

IdentityServer4 中AddInMemory的相關配置,都是 Mock 的(代碼配置),也能夠把這些配置存儲在數據庫中,這個後面再講。

AddInMemoryApiResources 增長的 API 資源服務(List 集合),也就此認證受權服務所管轄的 API 資源,好比上面配置的 api1,這個會在客戶端調用的時候用到,若是不一致,是不容許訪問的,另外,Clinet 中配置的AllowedScopes = { "api1" },表示此種受權模式容許的 API 資源集合(前提是須要添加ApiResource)。

配置很簡單,咱們也能夠訪問http://localhost:5000/.well-known/openid-configuration,查看具體的配置信息:

435188-20170111143441791-1201724230.png

2.2 API 資源服務配置

API 資源服務站點,須要添加程序包:

"IdentityServer4.AccessTokenValidation": "1.0.1"

添加一個ValuesController

[Route("[controller]")]
[Authorize]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Content("hello world");
    }
}

2.3 單元測試

須要添加程序包:

"IdentityModel": "2.0.0"

單元測試代碼:

[Fact]
public async Task ClientCredentials_Test()
{
    // request token
    var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
    var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
    var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");

    Assert.False(tokenResponse.IsError);
    Console.WriteLine(tokenResponse.Json);

    // call api
    var client = new HttpClient();
    client.SetBearerToken(tokenResponse.AccessToken);

    var response = await client.GetAsync("http://localhost:5010/values");
    Assert.True(response.IsSuccessStatusCode);
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine(content);
}

很簡單,和咱們以前用 ASP.NET WebApi OWIN 實現 OAuth 2.0 同樣,只不過配置和調用簡化了不少,由於 IdentityServer4 替咱們作了不少工做。

3. 密碼模式(resource owner password credentials)

簡單概述:客戶端提供 UserName 和 Password 給認證受權服務,驗證若是成功,返回 access_token,客戶端拿到 access_token,訪問 API 資源服務。

3.1 認證受權服務配置

建立 ASP.NET Core 站點,Startup 配置修改以下:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryApiResources(new List<ApiResource>
            {
                new ApiResource("api1", "My API")
            })
            .AddInMemoryClients(new List<Client>
            {
                // resource owner password grant client
                new Client
                {
                    ClientId = "ro.client",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    AllowedScopes = { "api1" }
                }
            })
            .AddTestUsers(new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "1",
                    Username = "xishuai",
                    Password = "123"
                }
            });
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(LogLevel.Debug);
        app.UseDeveloperExceptionPage();

        app.UseIdentityServer();
    }
}

和客戶端模式不一樣的是,AllowedGrantTypes受權模式改成了ResourceOwnerPassword,而後增長了測試用戶(用來驗證用戶名和密碼),也能夠存儲在數據庫中。

3.2 API 資源服務配置

API 資源服務站點,須要添加程序包:

"IdentityServer4.AccessTokenValidation": "1.0.1"

添加一個IdentityController

[Route("[controller]")]
[Authorize]
public class IdentityController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

3.3 單元測試

須要添加程序包:

"IdentityModel": "2.0.0"

單元測試代碼:

[Fact]
public async Task ResourceOwnerPassword_Test()
{
    // request token
    var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
    var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
    var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("xishuai", "123", "api1");

    Assert.False(tokenResponse.IsError);
    Console.WriteLine(tokenResponse.Json);

    // call api
    var client = new HttpClient();
    client.SetBearerToken(tokenResponse.AccessToken);

    var response = await client.GetAsync("http://localhost:5010/identity");
    Assert.True(response.IsSuccessStatusCode);
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine(JArray.Parse(content));
}

4. 簡化模式-With OpenID(implicit grant type)

簡化模式在 IdentityServer4 中的實現,就是 OpenID Connect。

簡單概述:客戶端肯定 URL(用戶認證服務),登陸在用戶認證服務,驗證成功,返回客戶端想要的用戶數據,並使此用戶爲登陸狀態,能夠在客戶端進行註銷用戶。

4.1 認證受權服務配置

建立 ASP.NET Core 站點,Startup 配置修改以下:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            })
            .AddInMemoryClients(new List<Client>
            {
                // OpenID Connect implicit flow client (MVC)
                new Client
                {
                    ClientId = "mvc",
                    ClientName = "MVC Client",
                    AllowedGrantTypes = GrantTypes.Implicit,

                    RedirectUris = { "http://localhost:5020/signin-oidc" },
                    PostLogoutRedirectUris = { "http://localhost:5020" },

                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile
                    }
                }
            })
            .AddTestUsers(new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "1",
                    Username = "xishuai",
                    Password = "123",
                    Claims = new List<Claim>
                    {
                        new Claim("name", "xishuai"),
                        new Claim("website", "http://xishuai.cnblogs.com")
                    }
                }
            });
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(LogLevel.Debug);
        app.UseDeveloperExceptionPage();

        app.UseIdentityServer();
    }
}

AddInMemoryIdentityResourcesAllowedScopes所配置的,是客戶端容許訪問的用戶信息,具體查看:Requesting Claims using Scope Values

ClientId 很重要,必須和客戶端一一對應,因此想要使用 OpenID 認證服務的客戶端,須要向提供 OpenID 認證服務的機構,申請一個 ClientId,OpenID 認證服務會統一發放一個用戶登陸的 URL。

TestUser中的Claims配置,其實就是IdentityServerConstants.StandardScopes.Profile

另外,還有用戶登陸的一些操做代碼,這邊就不貼了,能夠查看具體的實現:ImplicitServer.Web

4.2 客戶端服務配置

建立 ASP.NET Core 站點,添加程序包:

"Microsoft.AspNetCore.Authentication.Cookies": "1.0.*",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.*"

Startup 配置修改以下:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();

    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationScheme = "Cookies"
    });

    app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
    {
        AuthenticationScheme = "oidc",
        SignInScheme = "Cookies",

        Authority = "http://localhost:5001",
        RequireHttpsMetadata = false,

        ClientId = "mvc",
        SaveTokens = true
    });
    
    app.UseStaticFiles();
    app.UseMvcWithDefaultRoute();
}

UseOpenIdConnectAuthentication配置中的Authority,就是 OpenID 認證服務的 URL。

添加一個HomeController

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [Authorize]
    public IActionResult Secure()
    {
        ViewData["Message"] = "Secure page.";

        return View();
    }

    public async Task Logout()
    {
        await HttpContext.Authentication.SignOutAsync("Cookies");
        await HttpContext.Authentication.SignOutAsync("oidc");
    }

    public IActionResult Error()
    {
        return View();
    }
}

訪問 Secure 頁面,跳轉到認證服務地址,進行帳號密碼登陸,Logout 用於用戶的註銷操做。

4.3 Web 測試

435188-20170111155803103-1209138568.gif

5. 簡化模式-With OpenID & OAuth(JS 客戶端調用)

簡單概述:客戶端肯定 URL(用戶認證服務),登陸在用戶認證服務,驗證成功,返回客戶端想要的用戶數據 和 access_token,並使此用戶爲登陸狀態,能夠在客戶端進行註銷用戶,客戶端能夠拿到 access_token,去訪問受權範圍以內的 API 資源。

須要注意的是:由於簡化模式,因此 access_token 是做爲 URL 參數返回的。

5.1 認證受權服務配置

建立 ASP.NET Core 站點,Startup 配置修改以下:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            })
            .AddInMemoryApiResources(new List<ApiResource>
            {
                new ApiResource("api1", "My API")
            })
            .AddInMemoryClients(new List<Client>
            {
                // OpenID Connect implicit flow client (MVC)
                new Client
                {
                    ClientId = "js",
                    ClientName = "JavaScript Client",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowAccessTokensViaBrowser = true,

                    RedirectUris = { "http://localhost:5022/callback.html" },
                    PostLogoutRedirectUris = { "http://localhost:5022/index.html" },
                    AllowedCorsOrigins = { "http://localhost:5022" },

                    RequireConsent = false, //禁用 consent 頁面確認 https://github.com/IdentityServer/IdentityServer3/issues/863

                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api1"
                    }
                }
            })
            .AddTestUsers(new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "1",
                    Username = "xishuai",
                    Password = "123",
                    Claims = new List<Claim>
                    {
                        new Claim("name", "xishuai"),
                        new Claim("website", "http://xishuai.cnblogs.com")
                    }
                }
            });
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(LogLevel.Debug);
        app.UseDeveloperExceptionPage();

        app.UseIdentityServer();
    }
}

由於涉及到訪問 API 資源操做,須要須要添加AddInMemoryApiResources配置,AllowedScopes也須要添加對應的 API 資源名稱,AllowAccessTokensViaBrowser = true的配置的做用就是,能夠在瀏覽器地址中訪問 access_token。

更多實現代碼,點擊查看:ImplicitServerWithJS.Web

5.2 API 資源服務配置

API 資源服務站點,須要添加程序包:

"IdentityServer4.AccessTokenValidation": "1.0.1",
"Microsoft.AspNetCore.Cors": "1.1.0"

Startup 配置修改以下:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

    builder.AddEnvironmentVariables();
    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        // this defines a CORS policy called "default"
        options.AddPolicy("default", policy =>
        {
            policy.WithOrigins("http://localhost:5022")
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
    });

    services.AddMvcCore()
        .AddAuthorization()
        .AddJsonFormatters();
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseCors("default");

    app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
    {
        Authority = "http://localhost:5003",
        RequireHttpsMetadata = false,

        ApiName = "api1"
    });

    app.UseMvc();
}

由於 JS 須要跨域訪問 API 資源服務,因此須要增長 CORS 配置。

添加一個IdentityController

[Route("[controller]")]
[Authorize]
public class IdentityController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

5.3 JS Web 站點測試

建立一個 ASP.NET Core 站點,添加oidc-client.js前端組件,測試 JS 代碼:

/// <reference path="oidc-client.js" />

function log() {
    document.getElementById('results').innerText = '';

    Array.prototype.forEach.call(arguments, function (msg) {
        if (msg instanceof Error) {
            msg = "Error: " + msg.message;
        }
        else if (typeof msg !== 'string') {
            msg = JSON.stringify(msg, null, 2);
        }
        document.getElementById('results').innerHTML += msg + '\r\n';
    });
}

document.getElementById("login").addEventListener("click", login, false);
document.getElementById("api").addEventListener("click", api, false);
document.getElementById("logout").addEventListener("click", logout, false);

var config = {
    authority: "http://localhost:5003",
    client_id: "js",
    redirect_uri: "http://localhost:5022/callback.html",
    response_type: "id_token token",
    scope:"openid profile api1",
    post_logout_redirect_uri: "http://localhost:5022/index.html",
};
var mgr = new Oidc.UserManager(config);

mgr.getUser().then(function (user) {
    if (user) {
        log("User logged in", user.profile);
    }
    else {
        log("User not logged in");
    }
});

function login() {
    mgr.signinRedirect();
}

function api() {
    mgr.getUser().then(function (user) {
        var url = "http://localhost:5012/identity";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = function () {
            log(xhr.status, JSON.parse(xhr.responseText));
        }
        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
        xhr.send();
    });
}

function logout() {
    mgr.signoutRedirect();
}

測試過程(注意下 URL 中的參數):

435188-20170111164420510-1695158064.gif

6. 混合模式-With OpenID & OAuth(Hybrid Flow)

混合模式(Hybrid Flow)是一種新的模式,是簡化模式(implicit flow)和驗證碼模式(authorization code flow)的混合。

簡單概述:客戶端肯定 URL(用戶認證服務),登陸在用戶認證服務,驗證成功,返回客戶端想要的用戶數據 和 access_token,並使此用戶爲登陸狀態,能夠在客戶端進行註銷用戶,客戶端能夠拿到 access_token,去訪問受權範圍以內的 API 資源。

和上面的簡化模式流程差很少,不過 access_token 不是經過瀏覽器獲取的,而是經過後臺服務獲取。

6.1 認證受權服務配置

建立 ASP.NET Core 站點,Startup 配置修改以下:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddTemporarySigningCredential()
            .AddInMemoryIdentityResources(new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            })
            .AddInMemoryApiResources(new List<ApiResource>
            {
                new ApiResource("api1", "My API")
            })
            .AddInMemoryClients(new List<Client>
            {
                // OpenID Connect implicit flow client (MVC)
                new Client
                {
                    ClientId = "mvc",
                    ClientName = "MVC Client",
                    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },

                    RedirectUris = { "http://localhost:5021/signin-oidc" },
                    PostLogoutRedirectUris = { "http://localhost:5021" },

                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api1"
                    },
                    AllowOfflineAccess = true
                }
            })
            .AddTestUsers(new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "1",
                    Username = "xishuai",
                    Password = "123",
                    Claims = new List<Claim>
                    {
                        new Claim("name", "xishuai"),
                        new Claim("website", "http://xishuai.cnblogs.com")
                    }
                }
            });
    }

    public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(LogLevel.Debug);
        app.UseDeveloperExceptionPage();

        app.UseIdentityServer();
    }
}

AllowedGrantTypes配置改成HybridAndClientCredentialsAllowOfflineAccess須要設置爲true

更多實現代碼,點擊查看:HybridServer.Web

6.2 API 資源服務配置

API 資源服務站點,須要添加程序包:

"IdentityServer4.AccessTokenValidation": "1.0.1"

Startup 配置修改以下:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

    builder.AddEnvironmentVariables();
    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore()
        .AddAuthorization()
        .AddJsonFormatters();
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
    {
        Authority = "http://localhost:5002",
        RequireHttpsMetadata = false,

        ApiName = "api1"
    });

    app.UseMvc();
}

添加一個IdentityController

[Route("[controller]")]
[Authorize]
public class IdentityController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

6.3 客戶端服務配置

建立 ASP.NET Core 站點,添加程序包:

"Microsoft.AspNetCore.Authentication.Cookies": "1.0.*",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.*",
"IdentityModel": "2.0.0"

Startup 配置修改以下:

public Startup(IHostingEnvironment env)
{
    var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
        .AddEnvironmentVariables();

    Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationScheme = "Cookies"
    });

    app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
    {
        AuthenticationScheme = "oidc",
        SignInScheme = "Cookies",

        Authority = "http://localhost:5002",
        RequireHttpsMetadata = false,

        ClientId = "mvc",
        ClientSecret = "secret",

        ResponseType = "code id_token",
        Scope = { "api1", "offline_access" },

        GetClaimsFromUserInfoEndpoint = true,
        SaveTokens = true
    });

    app.UseStaticFiles();
    app.UseMvcWithDefaultRoute();
}

添加一個HomeController

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    [Authorize]
    public IActionResult Secure()
    {
        ViewData["Message"] = "Secure page.";

        return View();
    }

    public async Task Logout()
    {
        await HttpContext.Authentication.SignOutAsync("Cookies");
        await HttpContext.Authentication.SignOutAsync("oidc");
    }

    public IActionResult Error()
    {
        return View();
    }

    public async Task<IActionResult> CallApiUsingClientCredentials()
    {
        var tokenClient = new TokenClient("http://localhost:5002/connect/token", "mvc", "secret");
        var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");

        var client = new HttpClient();
        client.SetBearerToken(tokenResponse.AccessToken);
        var content = await client.GetStringAsync("http://localhost:5011/identity");

        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    }

    public async Task<IActionResult> CallApiUsingUserAccessToken()
    {
        var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");

        var client = new HttpClient();
        client.SetBearerToken(accessToken);
        var content = await client.GetStringAsync("http://localhost:5011/identity");

        ViewBag.Json = JArray.Parse(content).ToString();
        return Content("json");
    }
}

CallApiUsingClientCredentials是經過客戶端模式獲取 access_token,CallApiUsingUserAccessToken是經過上下文獲取保存的 access_token,其實和瀏覽器 URL 中獲取是同樣的意思,但須要配置SaveTokens = true

6.4 Web 測試

435188-20170111180830806-1510056788.gif

7. ASP.NET Core Identity and Using EntityFramework Core for configuration data

使用 ASP.NET Core Identity,就是用戶管理不禁 OpenID 認證服務進行提供,ASP.NET Core Identity 就至關於用戶的一個管理者,好比用戶的存儲等。

我沒作這一塊的示例,配置比較簡單:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();

    // Adds IdentityServer
    services.AddIdentityServer()
        .AddTemporarySigningCredential()
        .AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients())
        .AddAspNetIdentity<ApplicationUser>();
}

詳細使用:Using ASP.NET Core Identity

關於 IdentityServer4 的配置信息,可使用 EntityFramework Core 進行存儲,配置以下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    var connectionString = @"server=(localdb)\mssqllocaldb;database=IdentityServer4.Quickstart;trusted_connection=yes";
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    // configure identity server with in-memory users, but EF stores for clients and resources
    services.AddIdentityServer()
        .AddTemporarySigningCredential()
        .AddTestUsers(Config.GetUsers())
        .AddConfigurationStore(builder =>
            builder.UseSqlServer(connectionString, options =>
                options.MigrationsAssembly(migrationsAssembly)))
        .AddOperationalStore(builder =>
            builder.UseSqlServer(connectionString, options =>
                options.MigrationsAssembly(migrationsAssembly)));
}

詳細使用:Using EntityFramework Core for configuration data


最後,簡要總結下使用 IdentityServer4 的幾種應用場景:

  • 客戶端模式(Client Credentials):和用戶無關,用於應用程序與 API 資源的直接交互場景。

  • 密碼模式(resource owner password credentials):和用戶有關,通常用於第三方登陸。

  • 簡化模式-With OpenID(implicit grant type):僅限 OpenID 認證服務,用於第三方用戶登陸及獲取用戶信息,不包含受權。

  • 簡化模式-With OpenID & OAuth(JS 客戶端調用):包含 OpenID 認證服務和 OAuth 受權,但只針對 JS 調用(URL 參數獲取),通常用於前端或無線端。

  • 混合模式-With OpenID & OAuth(Hybrid Flow):推薦使用,包含 OpenID 認證服務和 OAuth 受權,但針對的是後端服務調用。

相關文章
相關標籤/搜索