2017-08-02 117 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。 –