【问题标题】:Identity Server 4 Authorization Code Flow exampleIdentity Server 4 授权代码流程示例
【发布时间】:2019-11-12 00:50:16
【问题描述】:

我正在尝试使用授权代码流通过 AspNet Core 实现 Identity Server 4。

问题是,github 上的IdentityServer4 存储库有几个示例,但没有一个带有授权代码流

有没有人有关于如何使用 Identity Server 4 和 MVC 中的客户端使用它来实现 授权代码流 的示例?

【问题讨论】:

    标签: c# asp.net-core identityserver4


    【解决方案1】:

    这是一个使用 Identity Server 4 和一个 MVC 客户端来使用它的授权代码流的实现。

    IdentityServer4 可以使用一个client.cs 文件来注册我们的MVC 客户端,它是ClientId、ClientSecret、允许的授权类型(在这种情况下是授权码),以及我们客户端的RedirectUri:

    public class Clients
    {
        public static IEnumerable<Client> Get()
        {
            var secret = new Secret { Value = "mysecret".Sha512() };
    
            return new List<Client> {
                new Client {
                    ClientId = "authorizationCodeClient2",
                    ClientName = "Authorization Code Client",
                    ClientSecrets = new List<Secret> { secret },
                    Enabled = true,
                    AllowedGrantTypes = new List<string> { "authorization_code" }, //DELTA //IdentityServer3 wanted Flow = Flows.AuthorizationCode,
                    RequireConsent = true,
                    AllowRememberConsent = false,
                    RedirectUris =
                      new List<string> {
                           "http://localhost:5436/account/oAuth2"
                      },
                    PostLogoutRedirectUris =
                      new List<string> {"http://localhost:5436"},
                    AllowedScopes = new List<string> {
                        "api"
                    },
                    AccessTokenType = AccessTokenType.Jwt
                }
            };
        }
    }
    

    IdentityServer4项目的Startup.cs的ConfigurationServices方法中引用了这个类:

        public void ConfigureServices(IServiceCollection services)
        {
            ////Grab key for signing JWT signature
            ////In prod, we'd get this from the certificate store or similar
            var certPath = Path.Combine(PlatformServices.Default.Application.ApplicationBasePath, "SscSign.pfx");
            var cert = new X509Certificate2(certPath);
    
            // configure identity server with in-memory stores, keys, clients and scopes
            services.AddDeveloperIdentityServer(options =>
                {
                    options.IssuerUri = "SomeSecureCompany";
                })
                .AddInMemoryScopes(Scopes.Get())
                .AddInMemoryClients(Clients.Get())
                .AddInMemoryUsers(Users.Get())
                .SetSigningCredential(cert);
    
            services.AddMvc();
        }
    

    作为参考,这里是上面引用的用户和作用域类:

    public static class Users
    {
        public static List<InMemoryUser> Get()
        {
            return new List<InMemoryUser> {
                new InMemoryUser {
                    Subject = "1",
                    Username = "user",
                    Password = "pass123",
                    Claims = new List<Claim> {
                        new Claim(ClaimTypes.GivenName, "GivenName"),
                        new Claim(ClaimTypes.Surname, "surname"), //DELTA //.FamilyName in IdentityServer3
                        new Claim(ClaimTypes.Email, "user@somesecurecompany.com"),
                        new Claim(ClaimTypes.Role, "Badmin")
                    }
                }
            };
        }
    }
    
    public class Scopes
    {
        // scopes define the resources in your system
        public static IEnumerable<Scope> Get()
        {
            return new List<Scope> {
                new Scope
                {
                    Name = "api",
                    DisplayName = "api scope",
                    Type = ScopeType.Resource,
                    Emphasize = false,
                }
            };
        }
    }
    

    MVC 应用程序需要两个控制器方法。第一种方法启动服务提供者(SP 发起)工作流。它创建一个 State 值,将其保存在基于 cookie 的身份验证中间件中,然后将浏览器重定向到 IdentityProvider (IdP)——在本例中是我们的 IdentityServer4 项目。

    public ActionResult SignIn()
    {
        var state = Guid.NewGuid().ToString("N");
    
        //Store state using cookie-based authentication middleware
        this.SaveState(state);
    
        //Redirect to IdP to get an Authorization Code
        var url = idPServerAuthUri +
            "?client_id=" + clientId +
            "&response_type=" + response_type +
            "&redirect_uri=" + redirectUri +
            "&scope=" + scope +
            "&state=" + state;
    
        return this.Redirect(url); //performs a GET
    }
    

    作为参考,这里是上面使用的常量和 SaveState 方法:

    //Client and workflow values
    private const string clientBaseUri = @"http://localhost:5436";
    private const string validIssuer = "SomeSecureCompany";
    private const string response_type = "code";
    private const string grantType = "authorization_code";
    
    //IdentityServer4
    private const string idPServerBaseUri = @"http://localhost:5000";
    private const string idPServerAuthUri = idPServerBaseUri + @"/connect/authorize";
    private const string idPServerTokenUriFragment = @"connect/token";
    private const string idPServerEndSessionUri = idPServerBaseUri + @"/connect/endsession";
    
    //These are also registered in the IdP (or Clients.cs of test IdP)
    private const string redirectUri = clientBaseUri + @"/account/oAuth2";
    private const string clientId = "authorizationCodeClient2";
    private const string clientSecret = "mysecret";
    private const string audience = "SomeSecureCompany/resources";
    private const string scope = "api";
    
    
    //Store values using cookie-based authentication middleware
    private void SaveState(string state)
    {
        var tempId = new ClaimsIdentity("TempCookie");
        tempId.AddClaim(new Claim("state", state));
    
        this.Request.GetOwinContext().Authentication.SignIn(tempId);
    }
    

    第二个 MVC 操作方法由 IdenityServer4 在用户输入其凭据并选中任何授权框后调用。动作方法:

    • 从查询字符串中获取授权码和状态
    • 验证状态
    • 发回 IdentityServer4 以交换访问令牌的授权码

    方法如下:

    [HttpGet]
    public async Task<ActionResult> oAuth2()
    {
        var authorizationCode = this.Request.QueryString["code"];
        var state = this.Request.QueryString["state"];
    
        //Defend against CSRF attacks http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html
        await ValidateStateAsync(state);
    
        //Exchange Authorization Code for an Access Token by POSTing to the IdP's token endpoint
        string json = null;
        using (var client = new HttpClient())
        {
            client.BaseAddress = new Uri(idPServerBaseUri);
            var content = new FormUrlEncodedContent(new[]
            {
                    new KeyValuePair<string, string>("grant_type", grantType)
                ,new KeyValuePair<string, string>("code", authorizationCode)
                ,new KeyValuePair<string, string>("redirect_uri", redirectUri)
                ,new KeyValuePair<string, string>("client_id", clientId)              //consider sending via basic authentication header
                ,new KeyValuePair<string, string>("client_secret", clientSecret)
            });
            var httpResponseMessage = client.PostAsync(idPServerTokenUriFragment, content).Result;
            json = httpResponseMessage.Content.ReadAsStringAsync().Result;
        }
    
        //Extract the Access Token
        dynamic results = JsonConvert.DeserializeObject<dynamic>(json);
        string accessToken = results.access_token;
    
        //Validate token crypto
        var claims = ValidateToken(accessToken);
    
        //What is done here depends on your use-case. 
        //If the accessToken is for calling a WebAPI, the next few lines wouldn't be needed. 
    
        //Build claims identity principle
        var id = new ClaimsIdentity(claims, "Cookie");              //"Cookie" matches middleware named in Startup.cs
    
        //Sign into the middleware so we can navigate around secured parts of this site (e.g. [Authorized] attribute)
        this.Request.GetOwinContext().Authentication.SignIn(id);
    
        return this.Redirect("/Home"); 
    }
    

    检查收到的状态是否符合您的预期有助于防御 CSRF 攻击:http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html

    此 ValidateStateAsync 方法将接收到的状态与保存在 cookie 中间件中的状态进行比较:

    private async Task<AuthenticateResult> ValidateStateAsync(string state)
    {
        //Retrieve state value from TempCookie
        var authenticateResult = await this.Request
            .GetOwinContext()
            .Authentication
            .AuthenticateAsync("TempCookie");
    
        if (authenticateResult == null)
            throw new InvalidOperationException("No temp cookie");
    
        if (state != authenticateResult.Identity.FindFirst("state").Value)
            throw new InvalidOperationException("invalid state");
    
        return authenticateResult;
    }
    

    此 ValidateToken 方法使用 Microsoft 的 System.IdentityModel 和 System.IdentityModel.Tokens.Jwt 库来检查 JWT 是否正确签名。

    private IEnumerable<Claim> ValidateToken(string token)
    {
        //Grab certificate for verifying JWT signature
        //IdentityServer4 also has a default certificate you can might reference.
        //In prod, we'd get this from the certificate store or similar
        var certPath = Path.Combine(Server.MapPath("~/bin"), "SscSign.pfx");
        var cert = new X509Certificate2(certPath);
        var x509SecurityKey = new X509SecurityKey(cert);
    
        var parameters = new TokenValidationParameters
        {
            RequireSignedTokens = true,
            ValidAudience = audience,
            ValidIssuer = validIssuer,
            IssuerSigningKey = x509SecurityKey,
            RequireExpirationTime = true,
            ClockSkew = TimeSpan.FromMinutes(5)
        };
    
        //Validate the token and retrieve ClaimsPrinciple
        var handler = new JwtSecurityTokenHandler();
        SecurityToken jwt;
        var id = handler.ValidateToken(token, parameters, out jwt);
    
        //Discard temp cookie and cookie-based middleware authentication objects (we just needed it for storing State)
        this.Request.GetOwinContext().Authentication.SignOut("TempCookie");
    
        return id.Claims;
    }
    

    包含这些源文件的工作解决方案位于 GitHub 上 https://github.com/bayardw/IdentityServer4.Authorization.Code

    【讨论】:

    • 我遇到了很多困难,似乎所有的教程/答案似乎都在使用一些旧形式的 IdentityServer。我知道这是一个直接针对版本 4 的问题;但是,即使在他们的文档页面上也存在差异。例如,它们不再有AddInMemoryScopesAddImMemoryUsers,也没有标准的User 类。 docs.identityserver.io/en/release/configuration/startup.html
    • 回购有点贬值。是否可以将其更新为与 .net core 2.0 和最新版本的 Identity server 4 兼容?
    【解决方案2】:

    这是一个示例 - 它使用混合流而不是代码流。但是,如果您的客户端库支持混合流(并且 aspnetcore 中间件也支持),则更推荐使用混合流。

    https://github.com/IdentityServer/IdentityServer4/tree/master/samples/Quickstarts/5_HybridFlowAuthenticationWithApiAccess

    【讨论】:

    • 您的链接已损坏。这就是为什么你应该把所有重要的部分都放在答案本身中。
    • 您能解释一下为什么更推荐使用混合流,或者至少引用该声明吗?
    • 因为它通过在 id_token 中包含代码的哈希值来防止代码剪切和粘贴攻击。
    • 链接已损坏。
    • 更新的链接已损坏。
    猜你喜欢
    • 2021-03-06
    • 2020-04-28
    • 1970-01-01
    • 2020-11-09
    • 2020-01-11
    • 2018-08-06
    • 2019-11-01
    • 2014-11-04
    • 2016-10-15
    相关资源
    最近更新 更多