C# Identity Server 4授权代码流示例

C# Identity Server 4授权代码流示例,c#,asp.net-core,identityserver4,C#,Asp.net Core,Identityserver4,我正在尝试使用授权代码流使用AspNet核心实现Identity Server 4 问题是,github上的存储库有几个示例,但没有一个具有授权代码流 有没有人有一个关于如何使用Identity Server 4和使用它的MVC客户端实现授权代码流的示例?这里有一个示例—它使用混合流而不是代码流。但是,如果您的客户端库支持混合流(aspnetcore中间件也支持),则更推荐使用混合流 下面是一个授权代码流的实现,它由Identity Server 4和一个MVC客户端使用 IdentitySer

我正在尝试使用授权代码流使用AspNet核心实现Identity Server 4

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


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

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


下面是一个授权代码流的实现,它由Identity Server 4和一个MVC客户端使用

IdentityServer4可以使用client.cs文件注册我们的MVC客户端,它是ClientId、ClientSecret、允许的授权类型(本例中为授权码)和客户端的重定向URI:

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
            }
        };
    }
}
以下是上面提到的用户和作用域类,仅供参考:

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,
            }
        };
    }
}
以下是上述使用的常量和保存状态方法,仅供参考:

//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);
}
在用户输入凭证并选中任何授权框后,IdenityServer4将调用第二个MVC操作方法。行动方法:

  • 从查询字符串中获取授权代码和状态
  • 验证状态
  • 发回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"); 
}
[HttpGet]
公共异步任务

此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;
}

专用异步任务

您的链接已断开。这就是为什么你应该把所有重要的部分都放在答案中的原因。嗨,多米尼克,我想现在是一个更好的链接。你能解释一下为什么混合流更受推荐,或者至少引用该声明?因为它通过在id_令牌中包含代码哈希来防止代码剪切粘贴攻击。链接已断开。我遇到的一个难题是,所有教程/答案似乎都在使用某种旧形式的IdentityServer。我知道这是一个直接针对版本4的问题;然而,即使在他们的文档页面上也存在差异。例如,它们不再有
附加的内存示波器
附加内存用户
,也不再有标准的
用户
类。回购有点贬值了。是否可以将其更新为与.net core 2.0和最新版本的Identity server 4兼容?
[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"); 
}
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;
}
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;
}