2016-05-18 17 views

risposta

21

Ecco un attuazione di un codice di autorizzazione di flusso con Identity Server 4 e un client MVC per consumarlo.

IdentityServer4 può utilizzare un file Client.cs per registrare il nostro cliente MVC, è ClientId, ClientSecret, tipi di sovvenzione consentiti (codice di autorizzazione in questo caso), e la RedirectUri del nostro cliente:

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

Questa classe viene fatto riferimento nel metodo ConfigurationServices dei Startup.cs nel progetto 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(); 
    } 

Per riferimento, qui sono utenti e classi Scopes riferimento sopra:

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

L'applicazione MVC richiede due metodi di controllo. Il primo metodo avvia il flusso di lavoro di Service Provider (SP-Initiated). Crea un valore di stato, lo salva nel middleware di autenticazione basato su cookie e quindi reindirizza il browser a IdentityProvider (IdP), il nostro progetto IdentityServer4 in questo caso.

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 
} 

Per riferimento, qui le costanti e metodo SaveState utilizzati sopra:

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

Il secondo metodo azione MVC viene chiamato dal IdenityServer4 dopo che l'utente immette le credenziali e verifica alcuna finestra di autorizzazione.Il metodo di azione:

  • afferra il codice di autorizzazione e Stato dalla stringa di query Stato
  • Convalida
  • POST torna a IdentityServer4 di scambiare il codice di autorizzazione per un token di accesso

Ecco il metodo :

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

Controllare che lo stato ricevuto sia quello che ci si aspetta difendersi da attacchi CSRF: http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html

Questo metodo ValidateStateAsync confronta lo stato ricevuto a ciò che è stato salvato al largo nel middleware biscotto:

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

Questo metodo utilizza ValidateToken System.IdentityModel e System.IdentityModel.Tokens.Jwt di Microsoft librerie per verificare che JWT sia firmato correttamente.

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

una soluzione di lavoro contenente i file di origine risiede su GitHub a https://github.com/bayardw/IdentityServer4.Authorization.Code

+2

Woa! Grazie per la risposta completa! –

+3

Qualcosa con cui ho molte difficoltà è che sembra che tutte le esercitazioni/risposte sembrano utilizzare una forma più vecchia di IdentityServer. So che questa è una domanda direttamente rivolta alla versione 4; tuttavia, anche sulla loro pagina di documentazione ci sono discrepanze. Ad esempio, non hanno più 'AddInMemoryScopes',' AddImMemoryUsers', e nessuna classe 'User' standard. http://docs.identityserver.io/en/release/configuration/startup.html – Adrian

4

Ecco un esempio: utilizza il flusso ibrido anziché il flusso del codice. Ma il flusso ibrido è più raccomandato in ogni caso se la libreria client lo supporta (e il middleware aspnetcore lo fa).

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

+5

Il link è rotto. Ecco perché dovresti mettere tutte le parti importanti nella risposta stessa. –

+0

Ciao Dominick, presumo che [questo] (https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/5_HybridFlowAuthenticationWithApiAccess) sia un collegamento migliore da usare adesso? – DavidG