Skip to content
On this page

Implicit

Implicit授权方式适用于公开客户端,如Web SPA等,Implicit授权省略了Authorization code授权方式的授权码环节,当用户在IdentityServer认证完成后直接返回Access Token给客户端应用,省略了客户端注册过程。

1. Identity Server

本节我们继续使用Authorization Code章节中的IdentityServer服务。下面我们简单来演示如何进行客户端注册。

csharp
public static IEnumerable<Client> Clients =>
    new[]
    {
        new Client
        {
            ClientId = "ImplicitJavaScriptClient",
            RequireClientSecret = false, //不需要客户端认证,所以不需要ClientSecret
            AllowedGrantTypes = GrantTypes.Implicit,
            AllowedScopes =
            {
                "WeatherApi",
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile
            },
            AllowAccessTokensViaBrowser = true,
            ClientUri = "https://localhost:8000",
            RedirectUris =
            {
                "https://localhost:8000/signin-oidc.html",
                "https://localhost:8000/silent.html"
            },
            PostLogoutRedirectUris = {"https://localhost:8000/signout-oidc.html"},
            AllowedCorsOrigins = {"https://localhost:8000"},
            RequireConsent = true, //是否需要用户点击同意
            AccessTokenLifetime = 5 * 60, //Implicit模式下Token有效时间一般设置较短
        }
    };
  • 因为不需要进行客户端认证所以我们将RequireClientSecret设为false,不再提供Client Secret
  • 因为客户端应用没有服务端,Access Token将会直接通过浏览器返回给客户端,所以需要设置AllowAccessTokensViaBrowsertrue
  • 如果客户端是Web应用,可以通过ClientUri设置其地址。
  • RedirectUrisPostLogoutRedirectUris分别设置登录和注销成功后要跳转的路径,与Authorization Code授权方式中用法相同。
  • 客户端是Web应用时,请求API一般会跨域,设置AllowedCorsOrigins属性允许客户端跨域即可。
  • RequireConsent表示是否需要用户手动点击同意授权。
  • Implicit模式下Access Token会直接暴露在公开客户端,出于安全考虑,一般会设置较短的有效期。

2. API

这里API项目依然使用Client Credentials中的代码,不再赘述。

Implicit客户端多为Web应用,此时就需要在API项目中开发客户端应用的跨域请求

csharp
public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddCors();
    // ...
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //...
    app.UseCors(policy =>
    {
        policy.WithOrigins("https://localhost:8000");
        policy.AllowAnyHeader();
        policy.AllowAnyMethod();
        policy.WithExposedHeaders("WWW-Authenticate");
    });
    //...
}

3. Client

考虑到有些读者对Angular/Vue/React等前端框架不了解,这里客户端应用我们就以最简单的原生JavaScript来演示。这里我们建立一个空的Asp.Net项目并注册DefaultFilesMiddlewareStaticFileMiddleware两个中间件,在wwwroot目录中建立静态文件即可。当然也可以不使用Asp.Net项目模板,直接建立一个纯前端项目也可。客户端代码已共享至Github

csharp
public class Startup
{
    public void Configure(IApplicationBuilder app) =>
        app.UseDefaultFiles().UseStaticFiles();
}

我们简单的使用Bootstrap 4.x来构建JS客户端,界面内容如下图所示。

js-oidc-client.png

IdentityServer提供了JavaScript SDK —— oidc-client

bash
npm install oidc-client
npm install bootstrap

3.1 UserManager

oidc-client库用于管理管理OIDC客户端会话和令牌等,其中最常用的类型是UserManager,它提供了登录/注销/令牌管理等一系列API,下面简单演示如何通过UserManager来创建Implicit客户端。

js
(function () {
    let mgr = new Oidc.UserManager({
        authority: "https://localhost:5000",
        client_id: "ImplicitJavaScriptClient",
        redirect_uri: window.location.origin + "/signin-oidc.html",
        post_logout_redirect_uri: window.location.origin + "/signout-oidc.html",
        silent_redirect_uri: window.location.origin + "/silent-oidc.html",
        automaticSilentRenew: true,
        response_type: "id_token token",
        scope: "WeatherApi openid profile",
        revokeAccessTokenOnSignout: true,
    });

    mgr.events.addUserSignedIn(function (e) {
        log("user logged in to the token server");
    });
    mgr.events.addUserSignedOut(function () {
        log("User signed out of OP");
    });
    mgr.events.addAccessTokenExpiring(function () {
        log("Access token expiring...");
        renewToken();
    });

    function showTokens() {
        mgr.getUser()
            .then(function (user) {
                if (!!user)
                    display("#identityData", user);
                else
                    log("Not logged in");
            });
    }
    showTokens();
})();
  • 构建UserManager对象时提供IdentityServer客户端基础配置。
  • 可以在客户端注册用户登录/注销/令牌过期的事件
  • UserManager.getUser()方法可以获取登录用户Identity Data等信息

3.2 SignIn

用户点击登录按钮时执行以下函数调用UserManager.signinRedirect(),浏览器会跳转到IdentiyServer引导用户进行身份认证。

js
function signIn() {
    mgr.signinRedirect();
}

用户认证并点击同意授权后,IdentityServer会重定向到我们设定的signin-oidc.html并将Id TokenAccess Token体现为URL中参数。

html
<script src="js/oidc-client.js"></script>
<script>
    new Oidc.UserManager()
        .signinRedirectCallback().then(function (user) {
        console.log(user);
        location.href = "/index.html"
    }).catch(function (e) {
        console.error(e);
    });
</script>

signin-oidc.html中引入以上代码,通过UserManager.signinRedirectCallback()函数完成登录回调。此函数会自动解析URL参数中的Id TokenAccess Token并将其保存在浏览器本地Session Storage中。登录过程完成后返回主页即可,主页中我们将用户信息显示在Identity data Card界面中。

3.3 SignOut

注销过程与登录类似。用户点击注销按钮时执行以下函数调用UserManager.signoutRedirect(),浏览器会跳转到IdentiyServer注销用户。

js
function signIn() {
    mgr.signinRedirect();
}

用户注销后,IdentityServer会重定向到我们设定的signout-oidc.html

html
<script src="js/oidc-client.js"></script>
<script>
    new Oidc.UserManager()
        .signoutRedirectCallback().then(function () {
        location.href = "/index.html"
    }).catch(function (e) {
        console.error(e);
    });
</script>

signout-oidc.html中引入以上代码,通过UserManager.signoutRedirectCallback()函数完成注销回调。此函数会自动清理浏览器本地Session Storage中的Id TokenAccess Token。注销过程完成后返回主页即可。

3.4 Call API

用户登录后使用其Access Token请求API即可。需要注意请求API前,要做好API/IdentityServer的跨域配置。另外还需注意,API端口最好不要使用Well Known Ports,否则可能会被浏览器拦截。

js
function callApi() {
    mgr.getUser().then(function (user) {
        if (!user)
            signIn();
        
        let xhr = new XMLHttpRequest();
        xhr.onload = function () {
            if (xhr.status < 400) {
                display("#api", JSON.parse(xhr.response));
                return;
            }
            if (xhr.status == 401)
                signIn();
            
            log({
                status: xhr.status,
                statusText: xhr.statusText,
                wwwAuthenticate: xhr.getResponseHeader("WWW-Authenticate")
            });
        }
        xhr.open("GET", "https://localhost:10000/WeatherForecast", true);
        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
        xhr.send();
    });
}

3.5 Renew Token

Implicit授权方式不允许使用Refresh Token,但oidc-client提供了SilentRenew方式来静默更新Token,设置automaticSilentRenewtrue,令牌过期会自动静默更新,该方式所有流程都保持静默执行,并不会跳转页面。调用UserManager.signinSilent()来静默登录更新Token。

js
function renewToken() {
    mgr.signinSilent()
        .then(function () {
            log("silent renew success");
            showTokens();
        }).catch(function (err) {
        log("silent renew error", err);
    });
}

静默登录后浏览器会执行silent_redirect_uri设定的silent-oidc.html

html
<script src="js/oidc-client.js"></script>
<script>new Oidc.UserManager().signinSilentCallback();</script>

silent-oidc.html中引入以上代码,通过UserManager.signinSilentCallback()函数完成静默登录回调。此函数会静默更新浏览器本地Session Storage中的Id TokenAccess Token

Released under the MIT License.