Categories
Uncategorized

Before and after the end of the separation structures scratch NetCore2.2 (EF Core CodeFirst + Autofac) + seven framework used to generate the Token JWT Vue project (personal opinion)

In the last mentioned in the request how the data model validation globally in NetCore project, just add the request validation feature in the model, when the interface using only the data used to use, without having to be concerned about whether the data is in line with business needs .

This will talk about some personal views on and use of JWT, the Internet can find a lot of relevant information and how to use, basically embedded directly into the Startup class to be used alone. Jwt the blogger is used as an authentication method. More convenient to use, and in doing verification and more flexible.

1. What is JWT?

Json web token (JWT), is a statement in order to pass between the network application execution environment based on open standards JSON ((RFC 7519) .JWT statements are typically used to deliver providers and service providers in the identity between being authenticated user identity information in order to obtain resources from the server, you can also add some additional business logic other information necessary to declare that the token can also be directly used for authentication.

Traditional session authentication

We know, http protocol itself is a stateless protocol, and this means that if the user provides a user name and password to our application for user authentication, then the next time request, the user should once again perform user authentication before OK, because, according to the http protocol, we can not know which user request is sent, so in order to make our application can identify which user request is issued, the information we can store a copy of the server user login, this login information It will be passed to the browser in response, telling save it as a cookie, so that next time the application is sent to our request, so that our application will be able to identify which user requests from, and this is the traditional session-based authentication.

But this session-based authentication so that the application itself is difficult to be expanded, with the increase of users of different clients, independent of the server is unable to carry more users, but this time the problem-based session authentication application will be exposed.

Session-based authentication problems revealed

Session: Each user authentication after our application, our application on the server must do a record, to facilitate the identification of the user’s next request, general session are stored in memory, and as the authenticated user increase in the cost of the server will be significantly increased.

Scalability: After user authentication, the server doing the authentication record, if authenticated records are stored in memory, then it means that the user requests the next request must also be on this server, so as to get the authorization of resources, so in a distributed application, the corresponding limits the ability of the load balancer. This also means that limits the scalability of applications.

CSRF: Because it is based on a cookie to identify the user, if the cookie is intercepted, the user could be vulnerable to cross-site request forgery attacks.

The token-based authentication mechanism

Similar to the http protocol is stateless token-based authentication mechanism, which does not require the server to retain the authentication information or session information of the user. This means that applications based on token authentication mechanism does not need to consider which server the user is logged in, which facilitated the application of the extension.

The process is this:

    • User for username and password to the server request

      Server to verify the user’s information

      The server sends to the user by verifying a token

      Client token memory, and each request included in this token value

      The server authentication token, and returns the data

JWT’s constitution

The first part we call the head (header), the second part we call the load (payload, similar to items carried on the plane), and the third part is the visa (signature).

header

jwt head carries two pieces of information:

    • Declared type, here is jwt

      Assertion of the encryption algorithm is usually used directly HMAC SHA256

Complete head like this in JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

Then the head base64-encryption (the encrypted can be decrypted symmetric), constitutes the first portion.

playload

Load local storage is valid information. The name refers specifically to such goods carried on the aircraft, these effective information consists of three parts

    • Standard registration statement

      Public Statement

      Private statement

Standard registration statement (recommended, but not mandatory to use):

    • iss: jwt issuer

      sub: jwt for the user

      aud: the receiving side jwt

      exp: jwt expiration time, the expiration date must be greater than the issue of time

      nbf: What time is defined before the jwt are not available.

      iat: jwt the issue of time

      jti: jwt unique identity, is mainly used as a one-time token, in order to avoid a replay attack.

Public statement: public declarations can add any information, general information about the user to add the necessary information or other business needs, but does not recommend adding sensitive information, because this part can be directly base64 decoded, can be seen inside information.

signature

The third part is a jwt visa information, this visa information consists of three parts:

    • header (after the base64)

      payload (after the base64)

    • secret

and using the payload header of this part needs after base64 base64 encryption encrypted connection string composed of salt and then encrypted by the secret encryption header compositions declared, and the third portion constitutes the jwt.

These three parts with connection into a complete string, constitutes the final JWT.

Note: secret is stored on the server side, the issue generated jwt also on the server side, secret is used to authenticate the issuance and jwt of jwt, so it is your server’s private key, in any scenario should not be revealed to go. Once the client has learned the secret, it means that the client can be self-signed jwt up.

2. How will JWT spun off generation and verification?

In any library (recommended on public classes) of NuGet package management added: System.IdentityModel.Tokens.Jwt class and then add TokenManager

    /// 
    /// token管理类
    /// 
    public class TokenManager
    {
        //

Recommended that private field into the configuration file

/// /// 秘钥 4的倍数 长度大于等于24 /// private static string _secret = "levy0102030405060708asdf"; /// /// 发布者 /// private static string _issuer = "levy"; /// /// 生成token /// ///

The data need to be signed

///

The default three days expired

///

Returns the token string

public static string GenerateToken(string tokenStr, int expireHour = 3 * 24) //

3 days overdue

{ var key1 = new SymmetricSecurityKey(Convert.FromBase64String(_secret)); var cred = new SigningCredentials(key1, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim("sid",tokenStr), //new Claim(ClaimTypes.Name,name), //

Examples of types may be used in ClaimTypes

}; var token = new JwtSecurityToken( issuer: _issuer,//

Issuer

notBefore: DateTime.Now,//

token can not be earlier than this time use

expires: DateTime.Now.AddHours(expireHour),//

Adding expiration time

claims: claims,//

Signature data

signingCredentials: cred//

signature

); //

I do not know to solve a problem PII anything unusual

IdentityModelEventSource.ShowPII = true; return new JwtSecurityTokenHandler().WriteToken(token); } /// /// 得到Token中的验证消息 /// /// /// /// public static string ValidateToken(string token, out DateTime dateTime) { dateTime = DateTime.Now; var principal = GetPrincipal(token, out dateTime); if (principal == null) return default(string); ClaimsIdentity identity = null; try { identity = (ClaimsIdentity)principal.Identity; } catch (NullReferenceException) { return null; } //identity.FindFirst(ClaimTypes.Name).Value; return identity.FindFirst("sid").Value; } /// /// 从Token中得到ClaimsPrincipal对象 /// /// /// private static ClaimsPrincipal GetPrincipal(string token, out DateTime dateTime) { try { dateTime = DateTime.Now; var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = (JwtSecurityToken)tokenHandler.ReadToken(token); if (jwtToken == null) return null; var key = Convert.FromBase64String(_secret); var parameters = new TokenValidationParameters() { RequireExpirationTime = true, ValidateIssuer = true,//

Verify publisher creates the token

ValidateLifetime = true,//

Check if the token is not expired, and the issuer's signature key is valid

ValidateAudience = false,//

To ensure that the recipient is entitled to receive a token of it

IssuerSigningKey = new SymmetricSecurityKey(key), ValidIssuer = _issuer//

Verify publisher creates the token

}; //

Verification token

var principal = tokenHandler.ValidateToken(token, parameters, out var securityToken); //

If the time is greater than the current start time or end time is less than the current time return null

if (securityToken.ValidFrom.ToLocalTime() > DateTime.Now || securityToken.ValidTo.ToLocalTime() < DateTime.Now) { dateTime = DateTime.Now; return null; } dateTime = securityToken.ValidTo.ToLocalTime();//

Return Token end time

return principal; } catch (Exception e) { dateTime = DateTime.Now; LogHelper.Logger.Fatal(e, "

Token authentication failed

"); return null; } } }

Then add a test method controller

        [HttpGet]
        [Route("testtoken")]
        public ActionResult TestToken()
        {
            var token = TokenManager.GenerateToken("

Test token generation

"); Response.Headers["token"] = token; Response.Headers["Access-Control-Expose-Headers"] = "token";//

Be sure to add this one or else the tip is to take less than token value field! Not to mention save the store.

return Succeed(token); }

Have to be mentioned here is the place if the front end of the separation of the items, due to the presence of cross-domain problem, a field have to add more Access-Control-Expose-Headers field value corresponding to the distal end of the need to obtain a set of fields in the header of the return , separated by commas.

Cause: When you cross-domain access, getResponseHeader XMLHttpRequest object () method can only get some basic response header, Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma, if you want to access other head, you need to set the server in response to the present head. Access-Control-Expose-Headers head for the server to allow the browser to access the head into the white list. Otherwise, the front and rear ends of the developer is prone to tear force oh ~

Test results screenshot:

Data can return to see it, can be seen in debugging. Then take this to the access interface. Blogger here is only an example, the specific operations as appropriate.

Next we take the generated token to the next visit will be successful verification, validation token, when we can look token passing if about to expire, if it is about to expire take a new token. Of course, there is one issue that is before the token can also be used. Here you can use other means to circumvent. The cache token expires judgment.

        [HttpPost]
        [Route("validtoken")]
        public ActionResult ValidToken([FromHeader]string token)
        {
            var str = TokenManager.ValidateToken(token, out DateTime date);
            if (!string.IsNullOrEmpty(str) || date > DateTime.Now)
            {
                //

When the token expiration time is less than five hours, updated token and return to the new token

if (date.AddHours(-5) > DateTime.Now) return Succeed($"

Token string: {str}, expires: {date}

"); var nToken = TokenManager.GenerateToken(str); Response.Headers["token"] = nToken; token = nToken; Response.Headers["Access-Control-Expose-Headers"] = "token"; } else { return Fail(101, "

Without obtaining authorization information

"); } return Succeed($"

Token string: {str}, expiration time: {DateTime.Now.AddHours (3 * 24)}

"); }

Test Results:

 

3. Questions and Discussion ~

JWT there are many areas of doubt, such as 1 has been stolen how to do? 2. User is in out of control? And so on.

Recommendation: 1 is not part of the payload to store sensitive information, and use https mode as much as possible, to prevent the possibility of theft and to alert users to the risk of not landing in a public place. Provide the user token save time selection, if not choose to save only the long-term deposit sessionStorage, chose the deposit localStorage.

2. The rear end of the user information is typically stored in cache among general users will not be long, so that the rear end of a short cache set time (e.g., 2 hours), when the rear end to take the cache expires on the payload information based on the user data portion memory cache, adding user information to determine whether the steady state value is available.

3. In order to solve the second data payload portion to be used, to prevent leakage, can be AES encrypt, decrypt taken out when required.

Above is an individual idea. Have any questions welcome to discuss.

 

Follow-up added:

Backend: New BaseUserController only need to override inherited BaseController OnActionExecuting method, add a verification is determined in the process. If there is an interface does not require validation in the controller, but also inherited the BaseUserController words,

AllowAnonymousAttribute attributes may be added to the interface to exclude authentication method. There is no distinction between use and refresh verification token, the individual feels that both are present the same problem, why not use one do?

BaseUserController class code

  

/// 
    /// 用户权限验证控制器
    /// 
    public abstract class BaseUserController : BaseController
    {
//        private UserModel _user;
//        /// 
//        /// 当前用户
//        /// 
//        protected new UserModel User
//        {
//            get => _user ?? (_user = _userCache.Current);//从缓存中取
//            set => _user = value;
//        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            if (filterContext.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
            {
                var isDefined = controllerActionDescriptor.MethodInfo.GetCustomAttributes(true)
                    .Any(a => a.GetType() == typeof(AllowAnonymousAttribute));
                if (isDefined)
                {
                    return;
                }
            }

            var token = Request.Headers["token"];
            if (string.IsNullOrEmpty(token))
            {
                filterContext.Result = new CustomHttpStatusCodeResult(200, 401, "

unauthorized

"); return; } var str = TokenManager.ValidateToken(token, out DateTime date); if (!string.IsNullOrEmpty(str) || date > DateTime.Now) { //

When the token expiration time is less than five hours, updated token and return to the new token

if (date.AddHours(-5) > DateTime.Now) return; var nToken = TokenManager.GenerateToken(str); Response.Headers["token"] = nToken; Response.Headers["Access-Control-Expose-Headers"] = "token"; return; } filterContext.Result = new CustomHttpStatusCodeResult(200, 401, "

unauthorized

"); } }

Add token test code will change before the next test code

public class TokenTestController : BaseUserController
    {
        [HttpGet]
        [Route("testtoken")]
        [AllowAnonymous]//

Allow everyone to access

public ActionResult TestToken() { var token = TokenManager.GenerateToken("

Test token generation

"); Response.Headers["token"] = token; Response.Headers["Access-Control-Expose-Headers"] = "token";//

Be sure to add this one or else the tip is to take less than token value field! Not to mention save the store.

return Succeed(token); } //[HttpPost] //[Route("validtoken")] //public ActionResult ValidToken([FromHeader]string token) //{ // var str = TokenManager.ValidateToken(token, out DateTime date); // if (!string.IsNullOrEmpty(str) || date > DateTime.Now) // { // //当token过期时间小于五小时,更新token并重新返回新的token // if (date.AddHours(-5) > DateTime.Now) return Succeed($"Token字符串:{str},过期时间:{date}"); // var nToken = TokenManager.GenerateToken(str); // Response.Headers["token"] = nToken; // token = nToken; // Response.Headers["Access-Control-Expose-Headers"] = "token"; // } // else // { // return Fail(101, "未取得授权信息"); // } // return Succeed($"Token字符串:{str},过期时间:{DateTime.Now.AddHours(3 * 24)}"); //} [HttpPost] [Route("validtoken")] public ActionResult ValidToken() { //

Token service processing verified in the base class

return Succeed("

success

"); } }

Then allows the test to look at the effect. Found to be not particularly stick ~ ~ ~ ~

  

Distal: Use Axios to manage perform the requested operation. Perfect use request token in response interceptor to handle information. All without concern token problem doing business.

  

Described with reference to part of the text:

  https://www.jianshu.com/p/576dbf44b2ae

  

The next one will be how and how MemoryCache NetCore Redis in the cache do not often change data to improve the response speed ~

 

Need source code under comments or private letter to me ~ SVN guest account password to download the code is not on GitHub.

Leave a Reply