2016-05-18 13 views

Odpowiedz

21

Oto wdrożenie Authorization Code przepływu z Identity Server 4 i klienta MVC, aby go konsumować.

IdentityServer4 można użyć pliku client.cs zarejestrować klientowi MVC, to ClientID, ClientSecret pozwolił rodzajów Grant (kod autoryzacji w tym przypadku) i RedirectUri naszego klienta:

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

Ta klasa jest wymieniony w metodzie ConfigurationServices z Startup.cs w projekcie IdentityServer4:

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(); 
    } 

odsyłającym oto Użytkownicy i zajęcia Celownik mowa powyżej:

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, "[email protected]"), 
        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, 
      } 
     }; 
    } 
} 

Aplikacja MVC wymaga dwóch metod kontrolera. Pierwsza metoda polega na uruchomieniu przepływu pracy dostawcy usług (SP-Initiated). Tworzy wartość stanu, zapisuje ją w oprogramowaniu pośredniczącym do uwierzytelniania opartym na plikach cookie, a następnie przekierowuje przeglądarkę do IdentityProvider (IdP) - naszego projektu IdentityServer4 w tym przypadku.

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 
} 

Dla porównania, tutaj są stałe i sposób SaveState wykorzystane powyżej:

//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); 
} 

Druga metoda działania MVC jest wywoływana przez IdenityServer4 gdy użytkownik wprowadzi ich poświadczenia i sprawdza wszystkie pola autoryzacji.Sposób działania:

  • chwyta kod autoryzacji i państwo z łańcucha zapytania
  • Sprawdza State
  • POST z powrotem do IdentityServer4 wymieniać kod autoryzacji na token dostępu

Oto metoda :

[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"); 
} 

Sprawdzanie, czy otrzymane państwo jest zgodne z oczekiwaniami bronić przed atakami CSRF: http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html

Ta metoda ValidateStateAsync porównuje otrzymane państwo do tego, co zostało zapisane off w middleware 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; 
} 

Ta metoda ValidateToken używa Microsoft System.IdentityModel i System.IdentityModel.Tokens.Jwt biblioteki, aby sprawdzić, czy JWT jest poprawnie podpisany.

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

roztworu roboczego zawierającego te pliki źródłowe rezyduje na GitHub na https://github.com/bayardw/IdentityServer4.Authorization.Code

+2

Woa! Dziękuję za odpowiedź! –

+3

Coś, z czym mam problem, wydaje się, że wszystkie samouczki/odpowiedzi wydają się używać starszej wersji serwera IdentityServer. Wiem, że jest to pytanie bezpośrednio skierowane do wersji 4; jednak nawet na ich stronie dokumentacji występują rozbieżności. Na przykład nie mają już "AddInMemoryScopes", "AddImMemoryUsers" ani żadnej standardowej klasy "User". http://docs.identityserver.io/en/release/configuration/startup.html – Adrian

4

Oto przykład - wykorzystuje przepływ hybrydowy zamiast przepływu kodu. Ale przepływ hybrydowy jest bardziej zalecany, jeśli biblioteka klienta go obsługuje (i to robi oprogramowanie pośredniczące aspnetcore).

https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/5_HybridFlowAuthenticationWithApiAccess

+5

Twój link jest uszkodzony. Dlatego należy umieścić wszystkie ważne elementy w samej odpowiedzi. –

+0

Witam Dominick, Zakładam [to] (https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/5_HybridFlowAuthenticationWithApiAccess) jest lepszym łączem do wykorzystania teraz? – DavidG

Powiązane problemy