即使使用 ASP.NET CORE 登录,我在 Angular 中也有 401 Unauthorized

Posted

技术标签:

【中文标题】即使使用 ASP.NET CORE 登录,我在 Angular 中也有 401 Unauthorized【英文标题】:I have a 401 Unauthorized in Angular even when login whit ASP.NET CORE 【发布时间】:2021-09-25 16:08:04 【问题描述】:

我已经在 Angular 中创建了一个登录名。登录后我可以登录,我得到一个 jwt 密钥。我的问题是当我登录时,我应该能够从我的 api 获取客户列表。我在这里遵循https://code-maze.com/authentication-aspnetcore-jwt-2/ 的指示,我不明白为什么当我尝试从我的 api 获取客户端时会得到 401。

这里是相关的角度文件

我有 auth-guard.service.ts

    import  Injectable  from '@angular/core';
    import  CanActivate, Router  from '@angular/router';
    import  JwtHelperService  from '@auth0/angular-jwt';
    
    @Injectable(
      providedIn: 'root'
    )
    export class AuthGuard implements CanActivate 
    
      constructor(private router: Router, private jwtHelper: JwtHelperService) 
    
    
      canActivate() 
        const token = localStorage.getItem("jwt");
    
        if (token && !this.jwtHelper.isTokenExpired(token)) 
          return true;
        
        this.router.navigate(["login"]);
        return false;
      
 

登录组件工作

@组件( 选择器:'应用程序登录', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] ) 导出类登录组件 无效登录:布尔值;

  constructor(private router: Router, private http: HttpClient)  

  login(form: NgForm) 
    const credentials = JSON.stringify(form.value);
    this.http.post("https://localhost:44363/api/auth/login", credentials, 
      headers: new HttpHeaders(
        "Content-Type": "application/json"
      )
    ).subscribe(response => 
      const token = (<any>response).token;
      localStorage.setItem("jwt", token);
      this.invalidLogin = false;
      this.router.navigate(["/"]);
    , err => 
      this.invalidLogin = true;
    );
   

这是我的 app.module

export function tokenGetter() 
  return localStorage.getItem("jwt");



@NgModule(
  declarations: [
    AppComponent,
    NavMenuComponent,
    HomeComponent,
    CounterComponent,
    FetchDataComponent,
    LocationManagerComponent,
    ClientsComponent,
    ClientDetailsComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule.withServerTransition( appId: 'ng-cli-universal' ),
    DataTablesModule,
    HttpClientModule,
    FormsModule,
    RouterModule.forRoot([
       path: '', component: HomeComponent, pathMatch: 'full' ,
       path: 'counter', component: CounterComponent ,
       path: 'fetch-data', component: FetchDataComponent ,
       path: 'location-manager', component: LocationManagerComponent ,
       path: 'clients', component: ClientsComponent, canActivate: [AuthGuard]  ,
       path: 'clients/:clientId', component: ClientDetailsComponent, canActivate: [AuthGuard] ,
       path: 'login', component: LoginComponent ,
    ]),
    BrowserAnimationsModule,
    JwtModule.forRoot(
      config: 
        tokenGetter: tokenGetter,
        whitelistedDomains: ["localhost:44363"],
        blacklistedRoutes: []
      
    )
  ],
  providers: [AuthGuard],
  bootstrap: [AppComponent]
)
export class AppModule 

Finnaly 这里是导致我的问题的文件。当我尝试从我的 api 获取客户端时,我的浏览器中出现 401。

@Component(
  selector: 'app-clients',
  templateUrl: './clients.component.html',
  styleUrls: ['./clients.component.css']
)
export class ClientsComponent implements OnInit 
  dtOptions: DataTables.Settings = ;
  public clients: Client[];

  constructor(private http: HttpClient)  


  ngOnInit(): void 
    this.dtOptions = 
      pagingType: 'full_numbers',
      pageLength: 10,
      processing: true
    ;
    this.http.get<Client[]>('https://localhost:44363/api/clients', 
      headers: new HttpHeaders(
        "Content-Type": "application/json"
      )
    ).subscribe(result => 
      this.clients = result;
    , error => 
      console.log(error)
    );
  

  onSubmit(closet: any, drawer: any) 
    //get the value by its property
    console.log("Closet: " + closet);
    console.log("Drawer: " + drawer);
  

这里是相关的 csharp 文件

在我的启动中,我对其进行了配置,以便使用 angular 并使用 jwt 键。

public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    
        services.AddCors(options =>
        
            options.AddPolicy("EnableCORS", builder => 
            
                builder.AllowAnyOrigin()
                .AllowAnyHeader()
                .AllowAnyMethod();
            );
        );

        services.AddAuthentication(opt =>
        
            opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        ).AddJwtBearer(opttions =>
        

            opttions.TokenValidationParameters = new TokenValidationParameters
            
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,

                ValidIssuer = "https://localhost:44363",
                ValidAudience = "https://localhost:44363",
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey@45"))
            ;
        );

        services.AddControllersWithViews();
       
        services.AddSpaStaticFiles(configuration =>
        
            configuration.RootPath = "ClientApp/dist";
        );
        
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddTransient<DatabaseMigrator>();
        services.AddDbContext<erp_colombiaDbContext>(options => options.Usemysql(
                 Configuration.GetConnectionString("DefaultConnection"),
                 optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));

        services.AddDbContext<erp_colombiaDbContext>(options => options.UseMySql(
                Configuration.GetConnectionString("DefaultConnection"),
                optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(DesignTimeDbContextFactory).Assembly.FullName)));
    

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
        
        else
        
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        

        app.UseHttpsRedirection();

        app.UseCors("EnableCORS");

        app.UseStaticFiles();
        if (!env.IsDevelopment())
        
            app.UseSpaStaticFiles();
        

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "controller/action=Index/id?");
        );

        app.UseSpa(spa =>
        
            spa.Options.SourcePath = "ClientApp";

            if (env.IsDevelopment())
            
                spa.UseAngularCliServer(npmScript: "start");
            
        );
    

这是我想从中获取客户端列表的控制器

// GET api/clients
[HttpGet]
[Authorize]
public IEnumerable<ClientViewModel> Get()

    ClientViewModel clientViewModel;
    List<ClientViewModel> listClientViewModels = new List<ClientViewModel>();

    var clients = Task.Run(async () => await _clientService.GetAllClients()).Result;

    foreach (var client in clients) 
    
        clientViewModel = new ClientViewModel();
        clientViewModel.ClientId = client.ClientId;
        clientViewModel.Active = client.Active;
        clientViewModel.Address = client.Address;
        clientViewModel.City = client.City;
        clientViewModel.ClienteName = client.ClienteName;
        clientViewModel.ComercialEmployeeId = client.ComercialEmployeeId;
        clientViewModel.Confirmed = client.Confirmed;
        clientViewModel.CountryId = client.CountryId;
        clientViewModel.CreationDate = client.CreationDate;
        clientViewModel.DANE = client.DANE;
        clientViewModel.Department = client.Department;
        clientViewModel.ElectronicBillingEmail = client.ElectronicBillingEmail;
        clientViewModel.Eliminated = client.Eliminated;
        clientViewModel.NIT = client.NIT;
        clientViewModel.PostalCode = client.PostalCode;
        clientViewModel.Phone = client.Phone;

        listClientViewModels.Add(clientViewModel);
    

    return listClientViewModels;

【问题讨论】:

【参考方案1】:

如果我没记错你得到的是 401,因为你的请求不是 Authorized

因此,您只需将 Authorization 标头添加到您的请求中,例如:

headers: new HttpHeaders(
    "Authorization": `Bearer $localStorage.getItem("jwt")`,
    "Content-Type": "application/json"
)

但我强烈建议使用Interceptor,它将为每个请求应用Authorization token(一次定义,无需复制代码\标题等)

@Injectable()
export class ApplyJWTTokenInterceptor implements HttpInterceptor 

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 

        const idToken = localStorage.getItem("jwt");

        if (idToken) 
            const authClonedRequest = req.clone(
                headers: req.headers
                    .set('Authorization', `Bearer $idToken`)
            );
            return next.handle(authClonedRequest);
        
        else 
            return next.handle(req);
        
    

建议检查:

    HTTPInterceptor Intercepting requests and responses Worked demo

【讨论】:

我不确定我应该在哪里创建类 ApplyJWTTokenInterceptor 我可以在 app 文件夹中直接创建文件吗?还有应该在哪里调用 ApplyJWTTokenInterceptor? 只需在提供程序中注入应用程序模块(另请查看推荐以获取更多信息和确切的示例项目) 我对使用不同的 HTTPInterceptor 很敏感。我将在我的问题中添加代码。 我相信 AuthGuard.service.ts 是我的 HTTpInterceptor? 那么 - 这取决于 .Net Core 部分 - 你必须找出设置有什么问题,然后这个问题不涉及角度。

以上是关于即使使用 ASP.NET CORE 登录,我在 Angular 中也有 401 Unauthorized的主要内容,如果未能解决你的问题,请参考以下文章

如何在Asp.net Core的登录过程中为“记住我”设置单个cookie超时?

为啥我在 Asp.Net CORE 中使用 JWT 获得 401 未经授权?

ASP.NET Core 3.1 MVC JWT 登录返回 401

登录 ASP.NET Core 3.1 后如何获取用户信息

如何在 ASP NET CORE 3.0 中获取当前登录的用户 ID?

为啥即使我调用 UseHttpsRedirection ASP.NET Core 重定向也不起作用?