series
- Develop blog projects based on ABP vNext and.NET Core – build projects using ABP CLI
- Develop blog projects based on ABP vNext and.NET Core – slim the project down and make it run
- Development blog project based on ABP vNext and.NET Core – Refinement and beautification, Swagger enter
- Develop blog project based on ABP vNext and.NET Core – data access and code priority
- Development blog project based on ABP vNext and.NET Core – add, delete, change and check custom warehouse
- Develop blog project based on ABP vNext and.NET Core – Uniform specification API, wrapper back model
- Develop blog projects based on ABP vNext and.NET Core – say Swagger, grouping, description, little green lock
- Develop blog projects based on ABP vNext and.NET Core – access GitHub and protect your API with JWT
- Develop blog project based on ABP vNext and.NET Core – exception handling and logging
- Develop blog projects based on ABP vNext and.NET Core – using Redis to cache data
- Develop blog project based on ABP vNext and.NET Core – integrate Hangfire for timed task processing
- Develop blog projects based on ABP vNext and.NET Core – Use AutoMapper to map objects
- Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (Part 1)
- Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (Part 2)
- Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (PART 3)
- Blog Development project based on ABP vNext and.NET Core
- Abp vNext and.NET Core
- Blog Development project based on ABP vNext and.NET Core
- Blog Development project based on ABP vNext and.NET Core
- Blog Development project based on ABP vNext and.NET Core
- Abp vNext and.NET Core Development Blog Project – Blazor
- Abp vNext and.NET Core Development Blog Project – Blazor – Part 2
- Abp vNext and.NET Core Development Blog Project – Blazor
- Abp vNext and.NET Core Development Blog Project – Blazor
- Abp vNext and.NET Core Development Blog Project – Blazor
- Abp vNext and.NET Core Development Blog Project – Blazor – Part 6
- Abp vNext and.NET Core Development Blog Project – Blazor
- Abp vNext and.NET Core Development Blog Project – Blazor Series (8)
- Abp vNext and.NET Core Development Blog Project – Blazor Series (9)
- Abp vNext and.NET Core development blog project – Final release project
Previous article (juejin.cn/post/684490…) The usage of Swagger is explained again, and the grouping and description of Swagger are completed, and the small green lock is opened for identity authentication and authorization. Then, identity authentication and authorization are discussed in this paper.
Before we start, we need to be clear about a few concepts. Please note that authentication and authorization mean different things. Authentication is to prove your identity. Authorization, that is, permission, is assigned to a user with a certain permission identifier, and the user can use a certain function of the system.
There are many ways for identity authentication, such as creating a user table and using account passwords, or accessing third-party platforms. Here, I accessed GitHub for identity authentication. Of course, you can choose other ways (such as QQ, wechat, weibo, etc.), you can expand.
Open GitHub and go to the developer Settings screen (github.com/settings/de…). , let’s create a new oAuth App.
As shown in the figure, we will use sensitive data in appSettings. json
{
...
"Github": {
"UserId": 13010050,
"ClientID": "5956811a5d04337ec2ca",
"ClientSecret": "8fc1062c39728a8c2a47ba445dd45165063edd92",
"RedirectUri": "https://localhost:44388/account/auth",
"ApplicationName": "阿星Plus"
}
}
Copy the code
ClientID and ClientSecret are generated by GitHub for us, please take care of your ClientID and ClientSecret. I’m giving it in plain text here, and I’ll delete the oAuth App 😝 at the end of this article. Please create your own!
RedirectUri is the callback address we added ourselves. ApplicationName is the name of our app, all of which corresponds to GitHub.
Read from appSettings. cs accordingly
./// <summary>
/// GitHub
/// </summary>
public static class GitHub
{
public static int UserId => Convert.ToInt32(_config["Github:UserId"]);
public static string Client_ID => _config["Github:ClientID"];
public static string Client_Secret => _config["Github:ClientSecret"];
public static string Redirect_Uri => _config["Github:RedirectUri"];
public static string ApplicationName => _config["Github:ApplicationName"]; }...Copy the code
Next, all of us to go to the official document request lot, developer.github.com/apps/buildi…
Analysis, we access GitHub identity authentication authorization process down into the following steps
- The GitHub redirect address is generated based on the parameters, and the GitHub login page is displayed for login
- After a successful login, it will jump to our callback address, which will carry
code
parameter - You get the code parameter, you get the access_token
- With access_token, you can invoke GitHub’s interface for obtaining user information to obtain the information about the current successful user
Before we get started, let’s take a quick look at GitHub’s API.
Create a githubconfig. cs configuration class under the Configurations folder in the.Domain layer and read out the required API and the contents of AppSettings. json.
//GitHubConfig.cs
namespace Meowv.Blog.Domain.Configurations
{
public class GitHubConfig
{
/// <summary>
///GET Redirects to the GitHub login page, obtains user authorization, and obtains the code
/// </summary>
public static string API_Authorize = "https://github.com/login/oauth/authorize";
/// <summary>
///POST request, get access_token according to code
/// </summary>
public static string API_AccessToken = "https://github.com/login/oauth/access_token";
/// <summary>
///GET request to obtain user information based on access_token
/// </summary>
public static string API_User = "https://api.github.com/user";
/// <summary>
/// Github UserId
/// </summary>
public static int UserId = AppSettings.GitHub.UserId;
/// <summary>
/// Client ID
/// </summary>
public static string Client_ID = AppSettings.GitHub.Client_ID;
/// <summary>
/// Client Secret
/// </summary>
public static string Client_Secret = AppSettings.GitHub.Client_Secret;
/// <summary>
/// Authorization callback URL
/// </summary>
public static string Redirect_Uri = AppSettings.GitHub.Redirect_Uri;
/// <summary>
/// Application name
/// </summary>
public static stringApplicationName = AppSettings.GitHub.ApplicationName; }}Copy the code
If you are careful, you may have noticed that we added a UserId to the configuration. The GitHub UserId is unique. I will configure my own UserId. When we get the UserId through the API and it is the same as the UserId we have configured, we will authorize it. You can go backstage and have fun.
Before we start writing the interface, there is still some work to be done to enable the use of our authentication and authorization in.NET Core, because. The HttpApi.Hosting layer references the project. Application, the Application layer itself also need to add Microsoft. AspNetCore. Authentication. JwtBearer, and so on. Application add package: Microsoft. AspNetCore. Authentication JwtBearer, Open the Package manager console with a command to Install – Package. Microsoft AspNetCore. Authentication. JwtBearer installation, so you don’t need to repeat the add reference.
In HttpApi. Hosting module class MeowvBlogHttpApiHostingModule, ConfigureServices added
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Authentication
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
ValidateIssuerSigningKey = true,
ValidAudience = AppSettings.JWT.Domain,
ValidIssuer = AppSettings.JWT.Domain,
IssuerSigningKey = new SymmetricSecurityKey(AppSettings.JWT.SecurityKey.GetBytes())
};
});
// Authentication and authorization
context.Services.AddAuthorization();
/ / Http requests
context.Services.AddHttpClient();
}
Copy the code
Because the forwarder will we need to call dead simple API in your code, so there will be ahead of System.Net.Http.IHttpClientFactory and related services are added to the IServiceCollection.
Explain what TokenValidationParameters parameter is:
ValidateIssuer: indicates whether the issuer is verified. ValidateAudience: Specifies whether to validate the access group. ValidateLifetime: Indicates whether to validate the lifetime. ClockSkew: Verifies the time offset of the Token. ValidateIssuerSigningKey: Indicates whether to verify the security key. ValidAudience: access group. ValidIssuer: indicates the issuer. IssuerSigningKey: indicates the security key. GetBytes() is an extension of ABP and can be used directly.
Set the values of ValidAudience, ValidIssuer, and IssuerSigningKey to True, and set the time offset to 30 seconds. Set the values of ValidAudience, ValidIssuer, and IssuerSigningKey to AppSettings. json.
//appsettings.json{..."JWT": {
"Domain": "https://localhost:44388"."SecurityKey": "H4sIAAAAAAAAA3N0cnZxdXP38PTy9vH18w8I9AkOCQ0Lj4iMAgDB4fXPGgAAAA=="."Expires": 30}}//AppSettings.cs.public static class JWT
{
public static string Domain => _config["JWT:Domain"];
public static string SecurityKey => _config["JWT:SecurityKey"];
public static int Expires => Convert.ToInt32(_config["JWT:Expires"]); }...Copy the code
Expires is our token expiration time, so we give 30 here as well. Whether it’s 30 minutes or 30 seconds is up to you.
The SecurityKey was generated using a random coding tool.
At the same time in OnApplicationInitialization (ApplicationInitializationContext context) used in it.
.public override void OnApplicationInitialization(ApplicationInitializationContext context)
{...// Authentication
app.UseAuthentication();
// Authentication and authorizationapp.UseAuthorization(); . }...Copy the code
Now that the configuration is complete, write the interface to generate the Token and apply it to Swagger.
Packages have been added before the.Application layer: Microsoft. AspNetCore. Authentication JwtBearer, direct the Authorize new folder, add an interface IAuthorizeService and implementation class AuthorizeService.
//IAuthorizeService.cs
using Meowv.Blog.ToolKits.Base;
using System.Threading.Tasks;
namespace Meowv.Blog.Application.Authorize
{
public interface IAuthorizeService
{
/// <summary>
///Obtain the login address (GitHub)
/// </summary>
/// <returns></returns>
Task<ServiceResult<string>> GetLoginAddressAsync();
/// <summary>
///Get AccessToken
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
Task<ServiceResult<string>> GetAccessTokenAsync(string code);
/// <summary>
///The login succeeds, and the Token is generated
/// </summary>
/// <param name="access_token"></param>
/// <returns></returns>
Task<ServiceResult<string>> GenerateTokenAsync(stringaccess_token); }}Copy the code
Add three interface member methods, all asynchronous, and note that we receive them using the return model we wrote earlier, and then implement them one by one.
First implement GetLoginAddressAsync(), let’s build an AuthorizeRequest object, used to populate the generated GitHub login address, in. ToolKits layer new GitHub folder, reference. Domain project, add the authorizerequest.cs class.
//AuthorizeRequest.cs
using Meowv.Blog.Domain.Configurations;
using System;
namespace Meowv.Blog.ToolKits.GitHub
{
public class AuthorizeRequest
{
/// <summary>
/// Client ID
/// </summary>
public string Client_ID = GitHubConfig.Client_ID;
/// <summary>
/// Authorization callback URL
/// </summary>
public string Redirect_Uri = GitHubConfig.Redirect_Uri;
/// <summary>
/// State
/// </summary>
public string State { get; set; } = Guid.NewGuid().ToString("N");
/// <summary>
///This parameter is optional. Multiple parameters to invoke Github are separated by commas (,), for example, scope=user,public_repo.
///If not, your app will only read the publicly gists in Github, such as publicly gists information, publicly repository information and gists information
/// </summary>
public string Scope { get; set; } = "user,public_repo"; }}Copy the code
The implementation method is as follows: Concatenate parameters and output the GitHub redirect address.
./// <summary>
///Obtain the login address (GitHub)
/// </summary>
/// <returns></returns>
public async Task<ServiceResult<string>> GetLoginAddressAsync()
{
var result = new ServiceResult<string> ();var request = new AuthorizeRequest();
var address = string.Concat(new string[]
{
GitHubConfig.API_Authorize,
"? client_id=", request.Client_ID,
"&scope=", request.Scope,
"&state=", request.State,
"&redirect_uri=", request.Redirect_Uri
});
result.IsSuccess(address);
return awaitTask.FromResult(result); }...Copy the code
Similarly, implement GetAccessTokenAsync(String code) and build the AccessTokenRequest object in. ToolKitsGitHub folder adds class: accesstokenRequest.cs.
//AccessTokenRequest.cs
using Meowv.Blog.Domain.Configurations;
namespace Meowv.Blog.ToolKits.GitHub
{
public class AccessTokenRequest
{
/// <summary>
/// Client ID
/// </summary>
public string Client_ID = GitHubConfig.Client_ID;
/// <summary>
/// Client Secret
/// </summary>
public string Client_Secret = GitHubConfig.Client_Secret;
/// <summary>
///Call the API_Authorize Code value obtained
/// </summary>
public string Code { get; set; }
/// <summary>
/// Authorization callback URL
/// </summary>
public string Redirect_Uri = GitHubConfig.Redirect_Uri;
/// <summary>
/// State
/// </summary>
public string State { get; set; }}}Copy the code
AccessToken is obtained based on the code that was successfully logged in to, since HTTP requests are involved, before that we need to dependency inject the IHttpClientFactory in the constructor and use the IHttpClientFactory to create the HttpClient.
.private readonly IHttpClientFactory _httpClient;
public AuthorizeService(IHttpClientFactory httpClient)
{ _httpClient = httpClient; }...Copy the code
./// <summary>
///Get AccessToken
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<ServiceResult<string>> GetAccessTokenAsync(string code)
{
var result = new ServiceResult<string> ();if (string.IsNullOrEmpty(code))
{
result.IsFailed("Code is empty");
return result;
}
var request = new AccessTokenRequest();
var content = new StringContent($"code={code}&client_id={request.Client_ID}&redirect_uri={request.Redirect_Uri}&client_secret={request.Client_Secret}");
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
using var client = _httpClient.CreateClient();
var httpResponse = await client.PostAsync(GitHubConfig.API_AccessToken, content);
var response = await httpResponse.Content.ReadAsStringAsync();
if (response.StartsWith("access_token"))
result.IsSuccess(response.Split("=") [1].Split("&").First());
else
result.IsFailed("Code is not correct");
returnresult; }...Copy the code
Creating an HttpClient using IHttpClientFactory automatically frees the object, sends a POST request to the HttpClient, and if GitHub returns a string with an Access_token, it succeeds. Let’s deal with the output access_token. If not, the parameter code is wrong.
Create a new AuthController in the.Httpapi layer, inject our IAuthorizeServiceService, and try out our interface.
//AuthController.cs
using Meowv.Blog.Application.Authorize;
using Meowv.Blog.ToolKits.Base;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc;
using static Meowv.Blog.Domain.Shared.MeowvBlogConsts;
namespace Meowv.Blog.HttpApi.Controllers
{
[ApiController]
[AllowAnonymous]
[Route("[controller]")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v4)]
public class AuthController : AbpController
{
private readonly IAuthorizeService _authorizeService;
public AuthController(IAuthorizeService authorizeService)
{
_authorizeService = authorizeService;
}
/// <summary>
///Obtain the login address (GitHub)
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("url")]
public async Task<ServiceResult<string>> GetLoginAddressAsync()
{
return await _authorizeService.GetLoginAddressAsync();
}
/// <summary>
///Get AccessToken
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
[HttpGet]
[Route("access_token")]
public async Task<ServiceResult<string>> GetAccessTokenAsync(string code)
{
return await_authorizeService.GetAccessTokenAsync(code); }}}Copy the code
Notice that we have added two attributes: [AllowAnonymous] and [ApiExplorerSettings(GroupName = Grouping.GroupName_v4)]. Add description to Swagger layer for AuthController
.new OpenApiTag {
Name = "Auth",
Description = "JWT Mode authentication and Authorization",
ExternalDocs = new OpenApiExternalDocs { Description = "JSON Web Token"}}...Copy the code
Open the Swagger document and call our two interfaces to see the effect.
Then open the redirection address we generated, it will jump to the login page, as follows:
Click Authorize button, the login will jump to our configured callback page,… /account/auth? code=10b7a58c7ba2e4414a14&state=a1ef05212c3b4a2cb2bbd87846dd4a8e
And then to get the code (10 b7a58c7ba2e4414a14), to call the AccessToken interface, success back our access_token eeafd5ca01b3719f74fc928440c89d59f2eeag (97).
Once you get the Access_token, you can call the get user information API. But before we do that, let’s write a couple of extension methods that we’ll use later and in the future. Create a folder called Extensions in the ToolKits layer and add a few more commonly used Extensions (…). .
I won’t post the code for the extension class. Go to GitHub(github.com/Meowv/Blog/…) Download it yourself, with specific annotations for each extension method.
GenerateTokenAsync(string access_token) is then implemented to generate the Token.
With access_token, you can directly call the interface that obtains user information: api.github.com/user?access… , and wrap this JSON in a model class, userResponse.cs.
If you need to convert JSON or XML to a model class, you can use one of the shortcuts in Visual Studio. Click on the upper-left menu: Edit => Paste optionally => Paste JSON as a class/paste XML as a class.
//UserResponse.cs
namespace Meowv.Blog.ToolKits.GitHub
{
public class UserResponse
{
public string Login { get; set; }
public int Id { get; set; }
public string Avatar_url { get; set; }
public string Html_url { get; set; }
public string Repos_url { get; set; }
public string Name { get; set; }
public string Company { get; set; }
public string Blog { get; set; }
public string Location { get; set; }
public string Email { get; set; }
public string Bio { get; set; }
public int Public_repos { get; set; }}}Copy the code
Then take a look at the specific way to generate tokens.
./// <summary>
///The login succeeds, and the Token is generated
/// </summary>
/// <param name="access_token"></param>
/// <returns></returns>
public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token)
{
var result = new ServiceResult<string> ();if (string.IsNullOrEmpty(access_token))
{
result.IsFailed("Access_token is empty");
return result;
}
var url = $"{GitHubConfig.API_User}? access_token={access_token}";
using var client = _httpClient.CreateClient();
client.DefaultRequestHeaders.Add("User-Agent"."Mozilla / 5.0 (Windows NT 10.0; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/ 537.36EDG /83.0.478.13");
var httpResponse = await client.GetAsync(url);
if(httpResponse.StatusCode ! = HttpStatusCode.OK) { result.IsFailed("Access_token is incorrect");
return result;
}
var content = await httpResponse.Content.ReadAsStringAsync();
var user = content.FromJson<UserResponse>();
if (user.IsNull())
{
result.IsFailed("User data not obtained");
return result;
}
if(user.Id ! = GitHubConfig.UserId) { result.IsFailed("Current account is not authorized");
return result;
}
var claims = new[] {
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMinutes(AppSettings.JWT.Expires)).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")};var key = new SymmetricSecurityKey(AppSettings.JWT.SecurityKey.SerializeUtf8());
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var securityToken = new JwtSecurityToken(
issuer: AppSettings.JWT.Domain,
audience: AppSettings.JWT.Domain,
claims: claims,
expires: DateTime.Now.AddMinutes(AppSettings.JWT.Expires),
signingCredentials: creds);
var token = new JwtSecurityTokenHandler().WriteToken(securityToken);
result.IsSuccess(token);
return awaitTask.FromResult(result); }...Copy the code
The GitHub API has a security mechanism. It is important to note that when we use code to simulate a request, we need to add user-agent to it, otherwise it will not return the result successfully.
FromJson
is an extension method we added earlier that converts JSON strings into solid objects.
SymmetricSecurityKey(byte[] key) takes a byte[] argument, and here we also use an extension method SerializeUtf8() string serialized into a sequence of bytes.
We judge whether the returned Id is the user Id we configured. If so, authentication succeeds, authorization is carried out, and Token is generated.
The Token generation code is also simple, specifying Name, Email, and an expiration time of 30 minutes. Specific the meaning can go here to see: tools.ietf.org/html/rfc751…
WriteToken(SecurityToken token) : new JwtSecurityTokenHandler().writeToken (SecurityToken token)
./// <summary>
///The login succeeds, and the Token is generated
/// </summary>
/// <param name="access_token"></param>
/// <returns></returns>
[HttpGet]
[Route("token")]
public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token)
{
return await_authorizeService.GenerateTokenAsync(access_token); }...Copy the code
Pass in the previously received access_token, and the calling interface can see that the token has been successfully generated.
[AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] This can be specified for the entire Controller as well as for specific interfaces.
When you want to protect an interface, you simply add Attribute: [Authorize]. Now to protect our BlogController under the query interface, adding [the Authorize], increased when Microsoft. Pay attention to the reference namespace AspNetCore. Authorization.
. ./// <summary>
///Add a blog
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
public async Task<ServiceResult<string>> InsertPostAsync([FromBody] PostDto dto)
...
/// <summary>
///Delete the blog
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Authorize]
public async Task<ServiceResult> DeletePostAsync([Required] int id).../// <summary>
/// Update the blog
/// </summary>
/// <param name="id"></param>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPut]
[Authorize]
public async Task<ServiceResult<string>> UpdatePostAsync([Required] int id, [FromBody] PostDto dto).../// <summary>
/// query blogs
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
public async Task<ServiceResult<PostDto>> GetPostAsync([Required] int id)... .Copy the code
Now compile and run, call the add, delete and change above and see if it works?
The interface will directly give us return a status code of 401 errors, in order to avoid this kind of unfriendly errors, we can add a middleware to handle our pipeline requests or in AddJwtBearer () in the treatment of our authentication event mechanism, when encounters an error status code, we still before returning to us the model created, Define friendly return errors, which will be described in a later chapter.
You can see that the small green lock is different between the public API and the API requiring authorization. The public API is displayed in black, and the API requiring authorization is displayed in gray.
What if we need to call our private API in Swagger? Click on our little green lock to fill in the tokens generated as Bearer {token}.
Be careful not to click Logout, otherwise you will exit.
As you can see, when we requested, there was an authorization: Bearer {token} in the request header and we were done. When we call from the Web, we just follow this rule.
Special note
When I did the authorization, the token was successfully generated and Bearer {token} was correctly filled in the Swagger. When the interface is called, 401 is always returned, and it is found that the cause of this problem is an incorrect name when configuring Swagger little green lock.
A unique name for the scheme, as per the Swagger spec.
As shown, change its name to “oauth2” and you can successfully authorize it. This article is connected to GitHub, to achieve authentication and authorization, using JWT way to protect our written API, have you learned? 😁 😁 😁
Open source: github.com/Meowv/Blog/…