即使使用 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的主要内容,如果未能解决你的问题,请参考以下文章