2017-08-02 114 views
1

我有一個使用Asp.Net Core後端的Angualar 4 SPA。我正在使用帶有JWT令牌的OpenIddict,身份驗證正常並返回令牌。只要我沒有使用[Authorize]註釋和控制器操作,應用程序就可以正常運行。當我這樣做,它總是會返回一個401.Angular 4 Asp.Net核心Web API Openiddict JWT授權總是返回401

說實話,我甚至不知道授權是如何工作的。我認爲當持票人標記被提供給用[Authorize]裝飾的請求時,中間件就會自動處理它。

我確實看到有一個EnableAuthorizationEndpoint可用,所以我用它,但該方法永遠不會被調用。所以我不確定我應該做什麼,所以我要在這裏展示我所做的事情,也許有人會很高興地指出我正確的方向。

所以這裏是我目前正在做的。首先,這是我的Angular登錄代碼。

 login(username: string, password: string): Observable<boolean> { 
     var url = this.apiUrl + '/connect/token'; 
     var body = 

`username=${username}&password=${password}&grant_type=password&scope=role`; 
     let headers: Headers = new Headers(); 
     headers.append('Content-Type', 'application/x-www-form-urlencoded'); 
     return this.http.post(url, body , { headers: headers }) 
      .map((response: Response) => { 
       // login successful if there's a jwt token in the response 
       let token = response.json() && response.json().access_token; 
       if (token) { 
        // set token property 
        this.token = token; 
        this.username = username; 

        // store username and jwt token in local storage to keep user logged in between page refreshes 
        localStorage.setItem('token', token); 
        localStorage.setItem('username', username); 

        // return true to indicate successful login 
        return true; 
       } else { 
        // return false to indicate failed login 
        return false; 
       } 
      }); 
    } 

這產生了一個令牌,我可以解析並看起來是正確的。

這裏是生成的令牌:

eyJhbGciOiJSUzI1NiIsImtpZCI6IkJEODE3RjE4NUVCRDM0MkQ0Q0NGNTgzNThFMUY3MThFMDkwRjk5MzYiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJGQjRFOUQ3Mi01QkQ4LTREM0ItOTc3QS0zMEIyRTU0NjI0MTkiLCJuYW1lIjoibWFydGluaG9ydG9uIiwicm9sZSI6WyJGYW4iLCJTUEZDQ2hpZWZzIiwiQ01TIEFkbWluIl0sInRva2VuX3VzYWdlIjoiYWNjZXNzX3Rva2VuIiwianRpIjoiYzhkODAzOWMtMzExNy00MGFjLWJmMjAtYTZlZTNlM2NlNzI5IiwiYXVkIjoicmVzb3VyY2Utc2VydmVyIiwibmJmIjoxNTAxNjQ3OTg1LCJleHAiOjE1MDE2NTE1ODUsImlhdCI6MTUwMTY0Nzk4NSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MzI0NC8ifQ.QLXt_IVEvat27Ut1OjBBMOPCTTULxXjmlg1skgI8gP6teE3BZLm3yzAzY9dyMeNKXli7dBMVh-PLwk_D0BRXrSTsm_Ufdc5f5z2hEnjhRA3rRM_nn8MxNLQ9RMAVLxBXyg_oyI9h2i_JX0LkqmNdn1ZiJ90_FCJ38vGXiCr9SAc7F47S3QqrI_gHqS-4lnurozj3zH0dzsxE2hCAiSMfHtu9WsFV7lCPONT9WsqX6muEtuJQaxmfcrRzhwFXutyso1v-iTtVnHukNkja9FnjVAt-arNSSAqS4GBmZjC9KOdrZ7fPE83yQXJLEeh7Wn1tIY-nebETu106fg5Zn5vdyAfR6wGAESbWg9FVt8QIlO06Cbq6Yubark-m3TlyXXBOv8-SLgv8I99nhra2bVsHAi2GeDKpmfdLPYmqiGsogztVJY-mte9WqQb25fYS-MfErQqzzxHnFxd8cy_lW_YFNyLVAfX1BTbQpuWRi_hvXqvX1vXHn-372s8JBUdii49udi081DXIUZAX2E0cRFt_5CreR_TR4fRDkzks4jyP3Qho2CEzM691s_V9n-orVxgOjDYd8U18h6Uswb8Xz2FU8knSCHjrjp8Vwc8s0A_b8KvkNFhODJ_f8mIS7glsjTGW3uts6J_gcoUbXy0MnizqKpMk0hTN4-3eOXemMny3Vyk 

我使用angular2,智威湯遜的所有API調用。配置如下:

export function authHttpServiceFactory(http: Http, options: RequestOptions) { 
    return new AuthHttp(new AuthConfig({ 
     noJwtError: true, 
     tokenName: 'token', 
     tokenGetter:() => localStorage.getItem('token'), 
     globalHeaders : [{'Content-Type': 'application/json'}] 
    }), http, options); 
} 

在瀏覽器中檢查顯示XHR請求都按預期形成。 接下來是啓動代碼。

public class Startup 
{ 
    public Startup(IHostingEnvironment env) 
    { 
     var builder = new ConfigurationBuilder() 
      .SetBasePath(env.ContentRootPath) 
      .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 
      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) 
      .AddEnvironmentVariables(); 
     Configuration = builder.Build(); 
    } 

    public IConfigurationRoot Configuration { get; } 

    // This method gets called by the runtime. Use this method to add services to the container. 
    public void ConfigureServices(IServiceCollection services) 
    { 
     // Add framework services. 
     services.AddDbContext<IdentityContext>(options => { 
      //options.UseSqlite(Configuration.GetConnectionString("FileConnection")); 
      options.UseSqlServer(Configuration.GetConnectionString("SqlConnection")); 
      options.UseOpenIddict(); 
     }); 

     services.AddCors(); 
     services.AddOptions(); 
     services.Configure<SIOptions>(Configuration); 

     services.AddIdentity<ApplicationUser, IdentityRole>(config => 
     { 
      config.Cookies.ApplicationCookie.AutomaticChallenge = false; 
     }) 

     /*services.AddIdentity<ApplicationUser, IdentityRole>()*/ 
     .AddEntityFrameworkStores<IdentityContext>() 
     .AddDefaultTokenProviders(); 


     // Configure Identity to use the same JWT claims as OpenIddict instead 
     // of the legacy WS-Federation claims it uses by default (ClaimTypes), 
     // which saves you from doing the mapping in your authorization controller. 
     services.Configure<IdentityOptions>(options => 
     { 
      options.ClaimsIdentity.UserNameClaimType = OpenIdConnectConstants.Claims.Name; 
      options.ClaimsIdentity.UserIdClaimType = OpenIdConnectConstants.Claims.Subject; 
      options.ClaimsIdentity.RoleClaimType = OpenIdConnectConstants.Claims.Role; 
      options.Cookies.ApplicationCookie.LoginPath = ""; 
      options.Cookies.ApplicationCookie.Events = new CookieAuthenticationEvents 
      { 
       OnRedirectToLogin = ctx => 
       { 
        if (ctx.Request.Path.StartsWithSegments("/api")) 
        { 
         //ctx.Response.StatusCode = (int)HttpStatusCode.Unauthorized; 
        } 
        else 
        { 
         ctx.Response.Redirect(ctx.RedirectUri); 
        } 
        return Task.FromResult(0); 
       } 
      }; 
     }); 

     services.AddOpenIddict() 
      // Register the Entity Framework stores. 
      .AddEntityFrameworkCoreStores<IdentityContext>() 
      // Register the ASP.NET Core MVC binder used by OpenIddict. 
      // Note: if you don't call this method, you won't be able to 
      // bind OpenIdConnectRequest or OpenIdConnectResponse parameters. 
      .AddMvcBinders() 
      // Enable the token endpoint. 
      .EnableTokenEndpoint("/connect/token") 
      .UseJsonWebTokens() 
      .AddSigningCertificate(new System.Security.Cryptography.X509Certificates.X509Certificate2(@"C:\Program Files (x86)\Windows Kits\10\bin\10.0.15063.0\x64\SIWWW.pfx", "Test123")) 
      //options.AddEphemeralSigningKey(); 
      // Enable the password flow. 
      .AllowPasswordFlow() 
      // During development, you can disable the HTTPS requirement. 
      .DisableHttpsRequirement() 
      .AllowAuthorizationCodeFlow() 
      .EnableAuthorizationEndpoint("/connect/authorize"); 
     ; 

     services.AddMvc(); 
     services.AddSingleton<IConfiguration>(Configuration); 

     // Add application services. 
     services.AddTransient<IEmailSender, AuthMessageSender>(); 
     services.AddTransient<ISmsSender, AuthMessageSender>(); 

     services.AddScoped<IPasswordHasher<ApplicationUser>, SqlPasswordHasher>(); 
    } 

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) 
    { 
     loggerFactory.AddConsole(Configuration.GetSection("Logging")); 
     loggerFactory.AddDebug(); 

     if (env.IsDevelopment()) 
     { 
      app.UseDeveloperExceptionPage(); 
      app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions { 
       HotModuleReplacement = true 
      }); 
     } 
     else 
     { 
      app.UseExceptionHandler("/Home/Error"); 
     } 

     app.UseCors(builder => 
      builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod() 
     ); 
     app.UseStaticFiles(); 
     app.UseIdentity(); 
     app.UseOAuthValidation(); 
     app.UseOpenIddict(); 


     app.UseMvc(routes => 
     { 
      routes.MapRoute(
       name: "default", 
       template: "{controller=Home}/{action=Index}/{id?}"); 
     }); 
     app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder => 
     { 
      builder.UseMvc(routes => 
      { 
       routes.MapSpaFallbackRoute(
        name: "spa-fallback", 
        defaults: new { controller = "Home", action = "Index" }); 
      }); 
     }); 

    } 
} 

這是我的驗證方法。

 [HttpPost("~/connect/token"), Produces("application/json")] 
    public async Task<IActionResult> Exchange(OpenIdConnectRequest request) 
    { 
     Debug.Assert(request.IsTokenRequest(), 
      "The OpenIddict binder for ASP.NET Core MVC is not registered. " + 
      "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called."); 


     if (request.IsPasswordGrantType()) 
     { 
      var user = await _userManager.FindByNameAsync(request.Username); 
      if (user == null) 
      { 
       return BadRequest(new OpenIdConnectResponse 
       { 
        Error = OpenIdConnectConstants.Errors.InvalidGrant, 
        ErrorDescription = "The username/password couple is invalid." 
       }); 
      } 

      // Ensure the user is allowed to sign in. 
      if (!await _signInManager.CanSignInAsync(user)) 
      { 
       return BadRequest(new OpenIdConnectResponse 
       { 
        Error = OpenIdConnectConstants.Errors.InvalidGrant, 
        ErrorDescription = "The specified user is not allowed to sign in." 
       }); 
      } 

      // Reject the token request if two-factor authentication has been enabled by the user. 
      if (_userManager.SupportsUserTwoFactor && await _userManager.GetTwoFactorEnabledAsync(user)) 
      { 
       return BadRequest(new OpenIdConnectResponse 
       { 
        Error = OpenIdConnectConstants.Errors.InvalidGrant, 
        ErrorDescription = "The specified user is not allowed to sign in." 
       }); 
      } 

      // Ensure the user is not already locked out. 
      if (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)) 
      { 
       return BadRequest(new OpenIdConnectResponse 
       { 
        Error = OpenIdConnectConstants.Errors.InvalidGrant, 
        ErrorDescription = "The username/password couple is invalid." 
       }); 
      } 

      // Ensure the password is valid. 
      if (!await _userManager.CheckPasswordAsync(user, request.Password)) 
      { 
       if (_userManager.SupportsUserLockout) 
       { 
        await _userManager.AccessFailedAsync(user); 
       } 

       return BadRequest(new OpenIdConnectResponse 
       { 
        Error = OpenIdConnectConstants.Errors.InvalidGrant, 
        ErrorDescription = "The username/password couple is invalid." 
       }); 
      } 

      if (_userManager.SupportsUserLockout) 
      { 
       await _userManager.ResetAccessFailedCountAsync(user); 
      } 

      // Create a new authentication ticket. 
      var ticket = await CreateTicketAsync(request, user); 

      var result = SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme); 
      return result; 
     } 

     return BadRequest(new OpenIdConnectResponse 
     { 
      Error = OpenIdConnectConstants.Errors.UnsupportedGrantType, 
      ErrorDescription = "The specified grant type is not supported." 
     }); 
    } 

    private async Task<AuthenticationTicket> CreateTicketAsync(OpenIdConnectRequest request, ApplicationUser user) 
    { 
     // Create a new ClaimsPrincipal containing the claims that 
     // will be used to create an id_token, a token or a code. 
     var principal = await _signInManager.CreateUserPrincipalAsync(user); 

     // Create a new authentication ticket holding the user identity. 
     var ticket = new AuthenticationTicket(principal, 
      new AuthenticationProperties(), 
      OpenIdConnectServerDefaults.AuthenticationScheme); 

     // Set the list of scopes granted to the client application. 
     ticket.SetScopes(new[] 
     { 
      OpenIdConnectConstants.Scopes.OpenId, 
      OpenIdConnectConstants.Scopes.Email, 
      OpenIdConnectConstants.Scopes.Profile, 
      OpenIddictConstants.Scopes.Roles 
     }.Intersect(request.GetScopes())); 

     ticket.SetResources("resource-server"); 

     // Note: by default, claims are NOT automatically included in the access and identity tokens. 
     // To allow OpenIddict to serialize them, you must attach them a destination, that specifies 
     // whether they should be included in access tokens, in identity tokens or in both. 

     foreach (var claim in ticket.Principal.Claims) 
     { 
      // Never include the security stamp in the access and identity tokens, as it's a secret value. 
      if (claim.Type == _identityOptions.Value.ClaimsIdentity.SecurityStampClaimType) 
      { 
       continue; 
      } 

      var destinations = new List<string> 
      { 
       OpenIdConnectConstants.Destinations.AccessToken 
      }; 

      // Only add the iterated claim to the id_token if the corresponding scope was granted to the client application. 
      // The other claims will only be added to the access_token, which is encrypted when using the default format. 
      if ((claim.Type == OpenIdConnectConstants.Claims.Name && ticket.HasScope(OpenIdConnectConstants.Scopes.Profile)) || 
       (claim.Type == OpenIdConnectConstants.Claims.Email && ticket.HasScope(OpenIdConnectConstants.Scopes.Email)) || 
       (claim.Type == OpenIdConnectConstants.Claims.Role && ticket.HasScope(OpenIddictConstants.Claims.Roles))) 
      { 
       destinations.Add(OpenIdConnectConstants.Destinations.IdentityToken); 
      } 

      claim.SetDestinations(destinations); 
     } 

     return ticket; 
    } 

最後這裏是一個控制器方法作品,未經[授權]罰款,否則返回401

 [Authorize] 
    [HttpGet("{id}"), Produces("application/json")] 
    public IActionResult Get(int id) 
    { 
     using (SIDB db = new SIDB()) 
     { 
      Exercises exer = db.Exercises.Include("Video").Where(ex => ex.Mode == 0 && ex.nExerciseId == id).Select(ex => ex).FirstOrDefault(); 
      if (exer == null) 
       return NotFound(); 
      return Json(new ExerciseReturnModel(exer, false)); 
     } 
    } 

控制器本身裝飾有:

[Route("api/activities")] 

我可能是在做一些非常愚蠢的事情,但我已經閱讀了所有我能找到的東西,但我無法實現它的工作。 任何幫助非常感謝。

回答

0

要使用JWT令牌而不是默認的 加密格式,則需要以下行。

options.UseJsonWebTokens(); 
options.AddEphemeralSigningKey(); 

我看到AddEphemeralSigningKey在您code.Not註釋掉,如果確認你已經測試了這一點。

希望這會有所幫助!

+0

我想AddEphemeralSigningKey原本只爲代替真正的證書的開發。我誤解了嗎? –

+0

我註釋了AddSigningCertificate幷包含了AddEphemeralSigningKey。答覆是一樣的。 –

0

當選擇JWT作爲訪問令牌格式時,不能使用aspnet-contrib驗證中間件。相反,您必須使用JWT中間件。

具體而言,你必須通過更換app.UseOAuthValidation();

app.UseJwtBearerAuthentication(new JwtBearerOptions 
{ 
    Authority = "[url of your OpenIddict-based app]", 
    Audience = "resource-server", 
    RequireHttpsMetadata = false 
}); 
+0

'app.UseJwtBearerAuthentication(新JwtBearerOptions { 管理局= 「[本地主機:53244]」, 觀衆= 「資源 - 服務器」, RequireHttpsMetadata =假 }); ' 我加了上面的代碼,但現在我得到500錯誤。並且調試跟蹤太大而不適合註釋。 它看起來並不像''工作 –

+0

@MartinHorton 500的響應是一個強烈的信號出現了一些問題與您的配置。你的「權威」是一個有效的URL嗎?它必須是'http:// localhost:53244 /'。 – Pinpoint

1

通過@Pinpoint給出的解決方案是必要的,但還不夠。另外,我必須改變我在控制器中裝飾方法的方式。取而代之的

[Authorize] 

它必須

[Authorize(ActiveAuthenticationSchemes = OAuthValidationDefaults.AuthenticationScheme)] 

有沒有辦法讓這個默認的,而不必重複上的每個方法?

+0

要回答我的問題,增加 'AuthenticationScheme = OAuthValidationDefaults.AuthenticationScheme' 在Startup.cs的JwtBearerOptions解決THI。 –