Angular 安全性 - 第 2 部分






4.93/5 (5投票s)
为我们的 Angular 应用程序添加安全性的第 2 部分。
引言
在 本系列文章的第 1 部分 中,您创建了一组用于用户及用户身份验证/授权的 Angular 类。您使用这些类登录用户,并在类中创建了一组属性来控制菜单和按钮的显示/隐藏。在本篇文章中,您将学习如何针对 Web API 方法进行用户身份验证。该方法将返回一个与您在 Angular 中创建的类具有相同属性的授权对象。您还将学习如何使用 JSON Web Tokens (JWT) 来保护您的 Web API 方法。您将使用 [Authorize]
属性来保护您的方法,并学习如何添加安全策略。
入门应用程序
要跟随本文进行学习,请下载配套的 ZIP 文件。解压 ZIP 文件中的示例后,有一个 VS Code 工作区文件,您可以使用它来加载此应用程序中的两个项目。双击此工作区文件,解决方案将被加载,如图 1 所示。有两个项目;PTC
是 Angular 应用程序。PtcApi
是 ASP.NET Core Web API 项目。
在上一篇文章中,您完成的所有工作都在客户端。对于本文的入门应用程序,我预先构建了一个 ASP.NET Core Web API 项目,并将 Product
和 Category
页面连接到了该 Web API 项目中的相应控制器。在本篇文章中,您将构建必要的安全类,以便将用户身份验证和授权信息返回给您的 Angular 应用程序。
创建应用程序安全类
在本系列文章的第 1 部分中,您创建了 Angular 类来表示用户和用户安全对象,其中包含可以绑定到的属性。您需要在 PtcApi
项目中创建相同的 C# 类。您将使用与 TypeScript 中相同的名称和几乎相同的属性来构建 C# 类。
AppUser 类
AppUser
类是用户类的简化版本,仅包含 UserName
和 Password
属性。在本篇文章中,我将保持类的简单性,并且不使用数据库,我将只使用模拟数据,就像我在本系列文章的第 1 部分中所做的那样。这有助于您专注于事物的工作原理,而无需担心大量配置。别担心,您将在文章中使用真实的 SQL 表。右键单击“Model”文件夹,然后添加一个名为 AppUser.cs 的新文件。将以下代码添加到此文件中。
namespace PtcApi.Model
{
public class AppUser
{
public int UserId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
}
创建 AppUserAuth 类
AppUserAuth
类具有表示用户安全配置文件的属性。同样,为了保持简单,我列出了可以直接绑定到 Angular UI 元素的单个属性。在未来的文章中,我将探讨如何将角色和声明/权限与此处介绍的安全系统结合使用。右键单击“Model”文件夹,然后添加一个名为 AppUserAuth.cs 的新文件。将以下代码添加到此文件中。
namespace PtcApi.Model
{
public class AppUserAuth
{
public AppUserAuth() : base()
{
UserName = string.Empty;
BearerToken = string.Empty;
IsAuthenticated = false;
CanAccessProducts = false;
CanAddProduct = false;
CanSaveProduct = false;
CanAccessCategories = false;
CanAddCategory = false;
}
public string UserName { get; set; }
public string BearerToken { get; set; }
public bool IsAuthenticated { get; set; }
public bool CanAccessProducts { get; set; }
public bool CanAddProduct { get; set; }
public bool CanSaveProduct { get; set; }
public bool CanAccessCategories { get; set; }
public bool CanAddCategory { get; set; }
}
}
构建 Security Manager 类
与其在 Web API 控制器类中构建模拟数据并创建用户安全对象,不如将该代码放在另一个类中。右键单击 PtcApi
项目,然后创建一个名为 Security 的新文件夹。右键单击新的 Security 文件夹,然后添加一个名为 SecurityManager.cs 的新文件。将以下代码添加到此文件中。
using System;
using System.Collections.Generic;
using System.Linq;
using PtcApi.Model;
namespace PtcApi.Security
{
public class SecurityManager
{
private AppUser _user = null;
private AppUserAuth _auth = null;
}
}
添加 CreateMockUsers() 方法
向 SecurityManager
类添加一个方法来创建一组模拟用户。您将根据此用户集合检查从 Angular 应用程序提交的用户凭据。
private List<appuser> CreateMockUsers()
{
List<appuser> list = new List<appuser>();
list.Add(new AppUser()
{
UserId = 1,
UserName = "PSheriff",
Password = "P@ssw0rd"
});
list.Add(new AppUser()
{
UserId = 2,
UserName = "Bjones",
Password = "P@ssw0rd"
});
return list;
}
编写 CreateMockSecurityObjects() 方法
如果您阅读了本系列文章的第 1 部分,以下方法应该类似于 PTC Angular 应用程序中创建的 LOGIN_MOCKS
常量。添加一个名为 CreateMockSecurityObjects()
的方法来构建一个 AppUserAuth
对象列表,其中为每个用户填充了不同的属性。
private List<appuserauth> CreateMockSecurityObjects()
{
List<appuserauth> list = new List<appuserauth>();
list.Add(new AppUserAuth()
{
UserName = "PSheriff",
BearerToken = "abi393kdkd9393ikd",
IsAuthenticated = true,
CanAccessProducts = true,
CanAddProduct = true,
CanSaveProduct = true,
CanAccessCategories = true,
CanAddCategory = false
});
list.Add(new AppUserAuth()
{
UserName = "Bjones",
BearerToken = "sd9f923k3kdmcjkhd",
IsAuthenticated = true,
CanAccessProducts = true,
CanAddProduct = false,
CanSaveProduct = false,
CanAccessCategories = true,
CanAddCategory = true
});
return list;
}
在上面的代码中,我已将一个值硬编码到 BearerToken
属性中。稍后在本篇文章中,您将使用 JWT 生成一个真实的 bearer token。
编写 BuildUserAuthObject 方法
当用户登录 Angular 应用程序时,用户名和密码会以 AppUser
对象的形式发送到一个控制器。该用户对象将被传递到您将在稍后编写的 ValidateUser()
方法中。编写一个名为 BuildUserAuthObject()
的方法,该方法将从 ValidateUser()
方法调用,以构建用户的安全对象。
此方法检查以确保名为 _user
的私有字段变量不为 null
。如果不是,它会创建模拟安全对象列表,并在列表中搜索匹配传入的用户名的一个对象。如果找到该对象,则将其分配给 _auth
字段。如果未找到用户安全对象,则创建 AppUserAuth
类的新实例并将其分配给 _auth
字段。返回到 Angular 应用程序的值就是此 _auth
字段中的值。
protected void BuildUserAuthObject()
{
if (_user != null)
{
_auth = CreateMockSecurityObjects()
.Where(u => u.UserName ==
_user.UserName).FirstOrDefault();
}
if (_auth == null)
{
_auth = new AppUserAuth();
}
}
添加 ValidateUser 方法
ValidateUser()
方法是 SecurityManager
类中唯一暴露的方法。您将要创建的接受来自 Angular 应用程序的 AppUser
对象的安全控制器会调用此方法,并将该用户对象传递进来以验证用户。
ValidateUser()
对象将传入的用户分配给 _user
字段。用户将根据模拟用户列表进行验证,以确保用户名和密码与列表中的某个用户匹配。然后调用 BuildUserAuthObject()
方法来构建一个 AppUserAuth
对象并将其返回给控制器。
public AppUserAuth ValidateUser(AppUser user)
{
// Assign current user
_user = user;
// Attempt to validate user
_user = CreateMockUsers().Where(
u => u.UserName.ToLower() == _user.UserName.ToLower()
&& u.Password == _user.Password).FirstOrDefault();
// Build User Security Object
BuildUserAuthObject();
return _auth;
}
添加 Security Controller
现在是时候构建 Angular 应用程序调用的安全控制器了。Angular 以 AppUser
对象的形式传递用户凭据。安全控制器返回一个 AppUserAuth
对象,其中包含适当设置的属性。右键单击“Controllers”文件夹,然后添加一个名为 SecurityController.cs 的新文件。将以下代码添加到此文件中。
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using PtcApi.Security;
using PtcApi.Model;
namespace PtcApi.Controllers
{
[Route("api/[controller]")]
public class SecurityController : Controller
{
}
}
在此新的控制器类中,添加一个接受 AppUser
对象的 Login()
方法。创建 SecurityManager
的实例,并将该 AppUser
对象传递给 ValidateUser()
方法。如果用户通过身份验证后返回,则 IsAuthenticated
属性将为 true
。将 ret
变量设置为由 StatusCode()
方法生成的 IActionResult
。将状态码 200 和用户安全对象的有效负载返回给 Angular 应用程序。如果用户未通过身份验证,则返回状态码 404。
[HttpPost("login")]
public IActionResult Login([FromBody]AppUser user)
{
IActionResult ret = null;
AppUserAuth auth = new AppUserAuth();
SecurityManager mgr = new SecurityManager();
auth = (AppUserAuth)mgr.ValidateUser(user);
if (auth.IsAuthenticated)
{
ret = StatusCode(StatusCodes.Status200OK, auth);
}
else
{
ret = StatusCode(StatusCodes.Status404NotFound,
"Invalid User Name/Password.");
}
return ret;
}
调用 Web API
要调用 SecurityController
的 Login()
方法,请使用 Angular 中的 HttpClient
服务。返回到 PTC
项目,并打开位于 \src\app\security 文件夹中的 security.service.ts 文件,然后添加以下 import
语句。
import {HttpClient, HttpHeaders} from '@angular/common/http';
import { tap } from 'rxjs/operators';
在 import
语句下方添加两个常量。第一个常量是 Web API 控制器的位置。第二个常量保存将数据 POST 到 Web API 调用所需的 HttpClient
的标头选项。
const API_URL = "https://:5000/api/security/";
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
要在安全服务类中使用 HttpClient
服务,请修改构造函数,告知 Angular 注入 HttpClient
。依赖注入在 Angular 中被广泛使用,以便将 HttpClient
等服务提供给不同的组件。HttpClientModule
已在 AppModule
类中导入。必须导入此模块才能允许组件使用 HttpClient
服务。
constructor(private http: HttpClient) { }
删除 login()
方法中的硬编码逻辑,并进行 Web API 调用。修改 login()
方法以调用 SecurityController
登录方法。如果从该调用返回有效数据,则使用 pipe()
方法来访问管道。您可以检索返回的数据并将其分配给 securityObject
属性。
在为 securityObject
属性赋值时,切勿使用等号。如果这样做,securityObject
的绑定引用将被擦除,并且绑定到 securityObject
属性的菜单和其他 UI 元素将不再工作。而是使用 Object.assign()
方法将数据从一个对象复制到另一个对象。
此方法还将获取 bearer token 并将其存储在本地存储中。这样做的原因将在本文稍后学习构建 HTTP Interceptor
类时变得显而易见。
login(entity: AppUser): Observable<appuserauth> {
// Initialize security object
this.resetSecurityObject();
return this.http.post<AppUserAuth>(API_URL + "login", entity, httpOptions).pipe(
tap(resp => {
// Use object assign to update the current object
// NOTE: Don't create a new AppUserAuth object
// because that destroys all references to object
Object.assign(this.securityObject, resp);
// Store into local storage
localStorage.setItem("bearerToken", this.securityObject.bearerToken);
}));
}
修改 Login Component
由于 Web API 可能返回 404: Not Found
错误,因此请务必处理此状态码,以便显示“Invalid User Name/Password
”错误消息。打开 login.component.ts 文件,并在 subscribe()
方法中添加错误处理块。登录 HTML 包含一个 Bootstrap 警报,其中有一个带有“Invalid User Name/Password
”字样的 <p>
标签。此警报仅在 securityObject
不为 null
且 isAuthenticated
属性设置为 false
时显示。
login() {
this.securityService.login(this.user)
.subscribe(resp => {
this.securityObject = resp;
if (this.returnUrl) {
this.router.navigateByUrl(this.returnUrl);
}
},
() => {
// Initialize security object to display error message
this.securityObject = new AppUserAuth();
});
}
试试看
保存所有更改,然后开始调试 Web API 项目。通过在终端窗口中键入 npm start
来启动 Angular 应用程序。在浏览器中,尝试使用“psheriff
”或“bjones
”以及密码“P@ssw0rd
”进行登录。如果一切顺利,Web API 现在将用于验证用户名和密码。尝试使用无效的登录 ID 和密码登录,以确保错误处理正在工作。
授权访问 Web API 调用
现在您已经创建了这些 Web API 调用,您需要确保只有经过授权的人才能调用您的 API。在 .NET 中,您可以使用 [Authorize]
属性来保护控制器类或控制器类中的单个方法。但是,.NET 运行时中必须有一个身份验证/授权组件来为该属性提供数据。[Authorize]
属性必须能够读取该数据以决定用户是否具有调用该方法的权限。
有许多不同的身份验证/授权组件可供选择,例如 Microsoft Identity、OAuth 和 JSON Web Tokens (JWT) 等。在本文中,您将使用 JWT。
保护 Product Get() 方法
为了向您展示应用 Authorize
属性到方法时会发生什么,请打开 ProductController.cs 文件,并将 [Authorize]
属性添加到 Get()
方法。
[HttpGet]
[Authorize]
public IActionResult Get()
{
// REST OF THE CODE
}
试试看
保存更改并运行 PtcApi
项目。以“psheriff
”身份登录,然后单击“Products”菜单。不会显示任何产品数据,因为您尝试调用一个已被 [Authorize]
属性保护的方法。按 F12 键打开开发人员工具,您应该会看到类似图 2 的内容。
请注意,您收到的状态码是 500,而不是 401 (Unauthorized) 或 403 (Forbidden)。原因是您尚未向 .NET Core 注册身份验证服务。必须将 Microsoft Identity、OAuth 或 JWT 注册到 .NET Core,才能返回 401 而不是 500。
添加 JWT 配置
要使用 JSON Web Token 系统,您必须执行几个步骤:
- 添加 JWT 包。
- 添加 JWT bearer token 检查包。
- 在配置文件中存储默认 JWT 设置。
- 将 JWT 注册为身份验证服务。
- 向 bearer token 添加选项以验证传入的 token。
- 构建 JWT token 并将其添加到用户安全对象中。
在 Web API 项目中,您首先要做的是添加一些包来使用 JSON Web Token 系统。打开 PtcApi
项目中的终端窗口,然后输入以下命令。
dotnet add package System.IdentityModel.Tokens.Jwt
添加 Bearer Token 检查
除了 Jwt 包之外,还添加一个包以确保 bearer token 从客户端传递。使用终端窗口中的以下命令将此包添加到 PtcApi
项目。
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
将 JWT 设置添加到配置
要签发 JwtToken
,需要做几件事:
- 用于对发送到客户端的数据进行哈希处理的密钥
- token 发行者的名称
- token 的目标受众
- 允许 token 有效的分钟数
将 JWT 信息存储在 appsettings.json 中
您将需要在代码中的两个地方使用以上所有项:一次在 .NET Core Startup 类中配置 JWT,另一次在为特定用户生成新 token 时。您不想将数据硬编码到两个地方,因此请使用 .NET Core 配置系统从 PtcApi
项目根文件夹中的 appsettings.json 文件中检索此数据。
打开 appsettings.json 文件,并添加一个名为 JwtSettings
的新条目。将以下属性和值添加到此 JwtSettings
对象中。
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
},
"JwtSettings": {
"key": "This*Is&A!Long)Key(For%Creating@A$SymmetricKey",
"issuer": "https://:5000",
"audience": "PTCUsers",
"minutesToExpiration": "10"
}
}
添加 JwtSettings 类
您可以使用 .NET core 中的配置系统来检索此数据,但我更喜欢使用一个类。右键单击 Security 文件夹,然后添加一个名为 JwtSettings.cs 的新文件,并添加以下代码。
public class JwtSettings {
public string Key { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int MinutesToExpiration { get; set; }
}
读取设置
打开 Startup.cs 文件,并添加一个新方法,使用 .NET Core 配置对象读取这些设置,并创建一个新的 JwtSettings
类实例。
public JwtSettings GetJwtSettings() {
JwtSettings settings = new JwtSettings();
settings.Key = Configuration["JwtSettings:key"];
settings.Audience = Configuration["JwtSettings:audience"];
settings.Issuer = Configuration["JwtSettings:issuer"];
settings.MinutesToExpiration =
Convert.ToInt32(
Configuration["JwtSettings:minutesToExpiration"]);
return settings;
}
创建 JWT 设置的单例
修改 ConfigureServices()
方法以创建 JwtSettings
类实例并调用 GetJwtSettings()
方法。创建此对象时,将其作为单例添加到 .NET core 服务中,以便您可以将其注入到任何控制器中。
public void ConfigureServices(IServiceCollection services)
{
// Get JWT Token Settings from JwtSettings.json file
JwtSettings settings;
settings = GetJwtSettings();
services.AddSingleton<JwtSettings>(settings);
// REST OF THE CODE HERE
}
将 JWT 注册为身份验证提供程序
在此代码下方,您将把 JWT 注册为身份验证提供程序。使用安全密钥、发行者、受众和过期分钟数来配置 JWT 的设置。此对象确保从 Angular 传递的 bearer token 有效。
public void ConfigureServices(IServiceCollection services)
{
// Get JWT Token Settings from JwtSettings.json file
JwtSettings settings;
settings = GetJwtSettings();
services.AddSingleton<JwtSettings>(settings);
// Register Jwt as the Authentication service
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = "JwtBearer";
options.DefaultChallengeScheme = "JwtBearer";
})
.AddJwtBearer("JwtBearer", jwtBearerOptions =>
{
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.Key)),
ValidateIssuer = true,
ValidIssuer = settings.Issuer,
ValidateAudience = true,
ValidAudience = settings.Audience,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(settings.MinutesToExpiration)
};
});
// REST OF THE CODE HERE
}
在 Startup
类中要做的最后一件事是修改 Configure()
方法,并告诉它使用身份验证。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// REST OF THE CODE HERE
app.UseAuthentication();
app.UseMvc();
}
将设置注入 SecurityController
您希望 .NET Core 将 JwtSettings
类注入到 SecurityController
中,以便您可以访问 JWT Token 设置。打开 SecurityController.cs 文件,并添加一个接受 JwtSettings
类实例的构造函数。将此 settings
对象存储在名为 _settings
的字段中。将此 _settings
字段传递给 SecurityManager
构造函数。
public class SecurityController : Controller
{
private JwtSettings _settings;
public SecurityController(JwtSettings settings)
{
_settings = settings;
}
[HttpPost("login")]
public IActionResult Login([FromBody]AppUser user)
{
IActionResult ret = null;
AppUserAuth auth = new AppUserAuth();
SecurityManager mgr = new SecurityManager(_settings);
auth = (AppUserAuth)mgr.ValidateUser(user);
// REST OF THE CODE HERE
}
在 SecurityManager 中接受设置
在上面的代码中,您将 JwtSettings
类的一个实例传递给了 SecurityManager
类。打开 SecurityManager.cs 文件,并添加代码以接受此 JwtSettings
实例。代码看起来将与 SecurityController
中添加的构造函数和 private
字段非常相似。
private JwtSettings _settings = new JwtSettings();
public SecurityManager(JwtSettings settings)
{
_settings = settings;
}
构建 JSON Web Token
您终于可以构建一个 bearer token 了,该 token 可以添加到用户安全对象中的 BearerToken
属性。向 SecurityManager
类添加一个名为 BuildJwtToken()
的新方法。从您在 Startup
类中使用的相同 Key
属性生成一个对称安全密钥。任何 JWT token 都需要的两个声明,Sub
和 Jti
,都应该被创建。在 Sub
声明中放入用户名,在 Jti
声明中创建一个该用户的唯一标识符。生成 Guid
是该字段的良好标识符。
接下来,您将添加一系列自定义声明。在这个简单的例子中,为 AppUserAuth
类中的每个属性创建一个声明。我相信您可以看到在哪里可以通过循环遍历来自表中的角色和/或声明/权限集合来添加声明。但是,我希望在本篇文章中保持简单。请注意,我使用 ToLower()
将“True
”和“False
”值转换为小写。这与 JavaScript/TypeScript 处理 true
和 false
的方式保持一致。
创建一个新的 JwtSecurityToken
对象,并使用与 Startup
类中相同的属性和 JwtSettings
类中的相同值进行设置。如果您不使用相同的值,那么当 token 从 Angular 传入时,它们将无法被验证。使用 JwtSecurityTokenHandler
类的 WriteToken()
方法将生成的 string
进行 base64
编码。正是这个 base64
编码的 string
被放入到返回给 Angular 的用户安全对象中的 BearerToken
属性。
protected string BuildJwtToken()
{
SymmetricSecurityKey key =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.Key));
// Create standard JWT claims
List<Claim> jwtClaims = new List<Claim>();
jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Sub, _user.UserName));
jwtClaims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
// Add custom claims
jwtClaims.Add(new Claim("isAuthenticated",
_auth.IsAuthenticated.ToString().ToLower()));
jwtClaims.Add(new Claim("canAccessProducts",
_auth.CanAccessProducts.ToString().ToLower()));
jwtClaims.Add(new Claim("canAddProduct", _auth.CanAddProduct.ToString().ToLower()));
jwtClaims.Add(new Claim("canSaveProduct", _auth.CanSaveProduct.ToString().ToLower()));
jwtClaims.Add(new Claim("canAccessCategories",
_auth.CanAccessCategories.ToString().ToLower()));
jwtClaims.Add(new Claim("canAddCategory", _auth.CanAddCategory.ToString().ToLower()));
// Create the JwtSecurityToken object
var token = new JwtSecurityToken(
issuer: _settings.Issuer,
audience: _settings.Audience,
claims: jwtClaims,
notBefore: DateTime.UtcNow,
expires: DateTime.UtcNow.AddMinutes(_settings.MinutesToExpiration),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);
// Create a string representation of the Jwt token
return new JwtSecurityTokenHandler().WriteToken(token); ;
}
调用 BuildJwtToken() 方法
找到 BuildUserAuthObject()
方法,并添加代码来调用此 BuildJwtToken()
方法,并将返回的 string
结果分配给 BearerToken
属性。
protected void BuildUserAuthObject()
{
if (_user != null)
{
_auth = CreateMockSecurityObjects()
.Where(u => u.UserName ==
_user.UserName).FirstOrDefault();
}
if (_auth == null)
{
_auth = new AppUserAuth();
}
else {
// Create JWT Bearer Token
_auth.BearerToken = BuildJwtToken();
}
}
试试看
保存所有更改,然后开始调试 PtcApi
项目。如果您已登录 Angular 应用程序,请单击“Logout”菜单。输入“psheriff
”或“bjones
”以及密码“P@ssw0rd
”,您应该会看到一个与图 3 类似的屏幕。
如果您想查看 bearerToken
属性的构成,可以在 www.jwt.io 网站上解码。将登录页面上的 bearerToken
复制到剪贴板。打开浏览器窗口并转到 www.jwt.io。向下滚动页面,直到看到一个标有“Encoded”的框。删除其中的内容,然后粘贴您的 bearer token。您应该会立即看到包含您所有数据的 payload 数据,如图 4 所示。
试试看
单击“Products”菜单,您现在应该会在开发人员工具控制台窗口中看到一个通用的 401 Unauthorized 消息,如图 5 所示。出现此错误的原因是服务器不知道您就是刚刚登录的同一个人。您必须在每次 Web API 调用中都传递 bearer token,以向服务器证明您有权调用该 API。
将标头添加到 Product Service
为避免出现状态码 401,您必须从 Angular 代码的每次 Web API 调用中都传递 bearer token。您将学习如何自动将 token 添加到每次调用中,但首先,只需修改产品服务类中的 Get()
方法。打开 product.service.ts 文件,并修改构造函数以注入 SecurityService
。
constructor(private http: HttpClient,
private securityService: SecurityService
) { }
在 getProducts()
方法中添加代码以创建新的 HttpHeaders
对象。实例化此对象后,调用 set()
方法,并将“Authorization
”作为标头名称传递。在此标头中传递的数据是单词 'Bearer
' 加上一个空格,然后是 bearer token 本身。向 HttpClient
的 get()
方法添加第二个参数,并传递一个具有 headers
属性的对象,以及您创建的 HttpHeaders
对象作为该属性的值。
getProducts(): Observable<Product[]> {
let httpOptions = new HttpHeaders()
.set('Authorization', 'Bearer ' + this.securityService.securityObject.bearerToken);
return this.http.get<Product[]>(API_URL<code>, { headers: httpOptions }</code>);
}
试试看
保存更改,转到浏览器并以“psheriff
”身份登录,然后单击“Products”菜单,您应该会看到产品数据已显示。这是因为服务器已验证了您的 token,并且现在知道您是谁。因此,您被授予了对 ProductController
中 Get()
方法的访问权限。
HTTP Interceptor
与其在每个 Web API 调用前都添加相同的标头,不如创建一个 HTTP Interceptor
类,将自定义标头添加到 Angular 的每个 Web API 调用中。右键单击 \security 文件夹,然后添加一个名为 http-interceptor.module.ts 的文件。我不会解释这段代码,因为它在 http://bit.ly/2GYt1H3 上有详细的文档,您应该能在互联网上找到许多关于它的博客文章。
我只想指出一点,您是从本地存储中检索 bearer token 的。您可能想知道为什么不将 SecurityService
对象注入到这个类中,并像在所有其他组件中一样从 SecurityService
对象中检索它。由于安全服务类也使用 HttpClient
,因此无法将 HTTP interceptor 注入 SecurityService
。这会导致递归错误。
如果您还记得,您在 SecurityService
类的 login()
方法中添加了将 bearer token 存储到本地存储的代码。现在您知道该代码的原因了。
import { Injectable, NgModule } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpEvent, HttpInterceptor, HttpHandler,
HttpRequest } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
var token = localStorage.getItem("bearerToken");
if(token) {
const newReq = req.clone(
{
headers: req.headers.set('Authorization', 'Bearer ' + token)
});
return next.handle(newReq);
}
else {
return next.handle(req);
}
}
};
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS,
useClass: HttpRequestInterceptor,
multi: true }
]
})
export class HttpInterceptorModule { }
为了使用这个 HTTP Interceptor
类,请在您的 AppModule
类中注册它。打开 app.module.ts 文件,并将此新模块添加到 imports
属性中。
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
AppRoutingModule<code>,
HttpInterceptorModule</code>
],
打开 product.service.ts 文件,并将 getProducts()
方法改回您最初的样子。删除创建 HttpHeaders
的代码,以及将其作为第二个参数传递给 get()
方法的代码。现在 getProducts()
方法应该如下所示。
getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(API_URL);
}
试试看
保存更改,转到浏览器,以“psheriff
”身份登录,然后尝试访问“Products”菜单。您应该仍然能从产品控制器获取数据。这验证了 HTTP Interceptor
类正在工作。
添加安全策略
[Authorize]
属性仅确保用户已通过身份验证。但是,对于某些 Web API 方法,您可能希望根据角色或声明/权限限制访问。这通过向 .NET Core Web API 项目的服务添加授权来实现。
打开 Startup.cs 文件,并在 ConfigureServices
方法中,紧挨着您之前添加的用于添加身份验证的代码下方,添加以下代码。对于 AppUserAuth
对象中的每个属性,您可以添加 policy
对象并指定用户必须拥有的值。
services.AddAuthorization(cfg =>
{
// NOTE: The claim key and value are case-sensitive
cfg.AddPolicy("CanAccessProducts", p =>
p.RequireClaim("CanAccessProducts", "true"));
});
创建声明后,您可以将 Policy
属性添加到 Authorize
属性,以检查任何声明名称。例如,在 ProductController.Get()
方法上,您可以将以下代码添加到 Authorize
属性中,以将该方法的用法限制为仅那些 CanAccessProducts
属性设置为 true
的用户。
[Authorize(Policy = "CanAccessProducts")]
试试看
打开 PtcApi
项目中的 SecurityManager.cs 文件,并找到 CreateMockSecurityObjects()
方法。将“CanAccessProducts
”声明修改为“PSheriff
”用户的“false
”值。
打开 app-routing.module.ts 文件,并删除“products
”路径的路由守卫。
{
path: 'products',
component: ProductListComponent
},
保存更改,然后开始调试 PtcApi
项目。返回浏览器,以“psheriff
”身份登录,然后在地址栏中键入 https://:4200/products,您现在应该会收到一个 403-Forbidden 错误。将路由守卫放回,并将 SecurityManager
类中的 CanAccessProducts
属性重置为“true
”值。
摘要
在本篇文章中,您构建了 Web API 调用来对用户进行身份验证,并将授权对象返回给 Angular。此外,您配置了 .NET Core Web API 项目以使用 JSON Web Token 系统来保护您的 Web API 调用。您学习了如何将 bearer token 发送到 Angular,以及如何通过 HTTP Interceptor
类让 Angular 将这些 token 发送回来。
历史
- 2018 年 7 月 16 日:初始版本