Use python to implement JWT authentication of the background system

Use python to implement JWT authentication of the background system

Column

Tea guest furu , Python Chinese community columnist

Blog:

http://www.jianshu.com/p/537b356d34c9

Today's article introduces an API authentication method suitable for restful+json. This method is based on jwt and adds some improvements borrowed from oauth2.0.

1. Several common ways to achieve authentication

First of all, we must understand that authentication and authentication are different. Authentication is to determine the legitimacy of a user, and authentication is to determine whether the user's authority level can perform subsequent operations. What I'm talking about here only includes certification. There are several ways to authenticate:

1.1 basic auth

This is the basic authentication carried in the http protocol, which is a simple authentication method. The principle is to add a string of username and password (formatted as "username:password", encoded in base64) in the header of each request.

This method is equivalent to binding "username:password" as an open certificate. There are several problems: ①Every request requires a username and password. If the connection does not use SSL/TLS, or the encryption is cracked , The user name and password are basically exposed; ②The user’s login status cannot be cancelled; ③The certificate will not expire unless the password is changed.

Generally speaking, the characteristic of this method is that it is simple but not safe.

Store the authentication result in the client's cookie, and check the identity information in the cookie as the authentication result. This method is characterized by its convenience, requires only one authentication, and can be used multiple times; you can also log out and set the expiration time; there are even ways (such as setting httpOnly) to avoid XSS attacks. But its shortcomings are very obvious. The use of cookies is a stateful service.

1.3 token

The JWT protocol seems to have been widely used, JSON Web Token-a token-based json format web authentication method. The basic principle is that the server issues a token in json format when the user name and password are passed for the first authentication. Subsequent client requests carry this token, and the server only needs to parse this token to determine the identity and legitimacy of the client. The JWT protocol only specifies the format of this protocol ( RFC7519 ), and its sequence generation method is described in the JWS protocol ( https://tools.ietf.org/html/rfc7515), which is divided into three parts:

1.3.1 header

①Declare the type, here is jwt

②Declare that the encryption algorithm usually directly uses HMAC SHA256, a common header is this:

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

Then base64 encode it.

1.3.2 payload

The payload is where the actual effective usage information is placed. JWT defines several things, including:

① Statements registered in the standard, such as issuer, recipient, effective time (exp), time stamp (iat, issued at), etc.; it is an official recommendation but not required;

②Public statement;

③Private declaration;

A common payload is this:

{'user_id': 123456,'user_role': admin,'iat': 1467255177}

In fact, the content in the payload is free, and you can add it according to your own development needs. Ps. There is a small problem. The result of token sequence generation using TimedJSONWebSignatureSerializer of itsdangerous package, exp is in the header. This seems to violate the rules of the jwt agreement.

1.3.3 signature

The serialized secreate key and salt key are stored. This part requires the base64-encrypted header and base64-encrypted payload to use the string composed of the connection. Then the salted secret combination is encrypted through the encryption method declared in the header, and then constitutes the third part of jwt.

2. Certification requirements

The target scenario is a back-end system with separated front-end and back-end for operation and maintenance. Although it is used on the intranet, it also has certain confidentiality requirements.

① The API is a stateless interface of restful+json, and the authentication is required to be the same mode

②Scalable horizontally

③Lower database pressure

④The certificate can be cancelled

⑤The certificate can be automatically extended

⑥Select JWT.

3. JWT implementation

3.1 How to generate token

The python module itsdangerous is used here. This module can do a lot of coding work, one of which is to implement the JWS token sequence. genTokenSeq This function is used to generate tokens. Among them, TimedJSONWebSignatureSerializer is used to generate the sequence. Here, the secret_key key and the salt value are read from the configuration file, of course, it can also be written directly here. Expires_in is the timeout interval. This interval is recorded in seconds and can be set directly here. I chose to set it as the formal parameter of the method (because this function is also used to solve the problem 2 mentioned below).

# serializer for JWTfrom itsdangerous import TimedJSONWebSignatureSerializer as Serializer """
    token is generated as the JWT protocol.
    JSON Web Tokens(JWT) are an open, industry standard RFC 7519 method
    """
    def genTokenSeq(self, expires):
        s = Serializer(
            secret_key=app.config['SECRET_KEY'],
            salt=app.config['AUTH_SALT'],
            expires_in=expires)
        timestamp = time.time() return s.dumps(
            {'user_id': self.user_id,'user_role': self.role_id,'iat': timestamp}) 
        # The token contains userid, user role and the token generation time.
        # u can add sth more inside, if needed.
        #'iat' means'issued at'. claimed in JWT.

Using this Serializer can help us deal with header and signature issues. We only need to use s.dumps to write the contents of the payload. Here I am going to write three values ​​in each token: user id, user role id and current time ('iat' is one of the JWT standard registration statements).

Suppose the information I write is {"iat": 1467271277.131803, "user_id": "46501228343b11e6aaa6a45e60ed5ed5f973ba0fcf783bb8ade34c7b492d9e55", "user_role": 3}

The token generated by the above method is

eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NzM0MTQ3NCwiaWF0IjoxNDY3MzM3ODc0fQ.eyJpYXQiOjE0NjczMzc4NzQuNzE3MDYzLCJ1c2VyX2lkIjoiNDY1MDEyMjgzNDNiMTFlNmFhYTZhNDVlNjBlZDVlZDVmOTczYmEwZmNmNzgzYmI4YWRlMzRjN2I0OTJkOWU1NSIsInVzZXJfcm9sZSI6M30.23QD0OwLjdioKu5BgbaH2gHT2GoMz90n8VZcpvdyp7U

It is composed of "header.payload.signature".

3.2 How to parse the token

The analysis needs to use the same serializer, configure the same secret key and salt, and use the loads method to parse the token. itsdangerous provides a variety of exception handling classes, which are also very convenient to use. If it is SignatureExpired, it can directly return to expire; if it is BadSignature, it represents all other signature errors, so it is divided into:

① The payload can be read: then the message is a message whose content has been tampered with and the message body encryption process is correct, and the secret key and salt are likely to be leaked;

②The payload cannot be read: The message body is directly tampered with, and the secret key and salt should still be safe.

The above content is written as a function to verify the user token. If implemented in python flask, you can consider changing this function to a decorator decorating paint. Put the decorator @ in front of all methods that need to verify the token, and the code can be more elegant.

# serializer for JWTfrom itsdangerous import TimedJSONWebSignatureSerializer as Serializer# exceptions for JWTfrom itsdangerous import SignatureExpired, BadSignature, BadData# Class xxx# after definition of your class, here goes the auth method:
    def tokenAuth(token):
        # token decoding
        s = Serializer(
            secret_key=api.app.config['SECRET_KEY'],
            salt=api.app.config['AUTH_SALT']) try:
            data = s.loads(token) # token decoding faild
            # if it happend a plenty of times, there might be someone
            # trying to attact your server, so it should be a warning.
        except SignatureExpired:
            msg ='token expired'
            app.logger.warning(msg) return [None, None, msg] except BadSignature, e:
            encoded_payload = e.payload if encoded_payload is not None: try:
                    s.load_payload(encoded_payload) except BadData: # the token is tampered.
                    msg ='token tampered'
                    app.logger.warning(msg) return [None, None, msg]
            msg ='badSignature of token'
            app.logger.warning(msg) return [None, None, msg] except:
            msg ='wrong token with unknown reason'
            app.logger.warning(msg) return [None, None, msg] if ('user_id' not in data) or ('user_role' not in data):
            msg ='illegal payload inside'
            app.logger.warning(msg) return [None, None, msg]
        msg ='user(' + data['user_id'] +') logged in by token.'# app.logger.info(msg)
        userId = data['user_id']
        roleId = data['user_role'] return [userId, roleId, msg]

The mechanism of inspection and judgment is as follows:

1. Use the encrypted class, then use it for decryption (using the previous key and salt value), and save the result into data;

2. If the SignatureExpired exception is caught, it means that the token has expired according to the expired setting in the token, and it returns'token expired';

3. If it is other BadSignature anomaly, it should be divided into: 4. If the payload is still complete, then parse the payload. If the BadData anomaly is caught, it means that the token has been tampered with and return'token tampered'; 5. If the payload is incomplete, go directly Return'badSignature of token';

6. If none of the above exceptions are correct, it can only return the unknown exception'wrong token with unknown reason';

7. Finally, if the data can be parsed normally, take out the data in the payload and verify whether there is legal information in the payload (here is the json data of the user_id and user_role keys). If the data is illegal, return'illegal payload inside' '. Once this happens, it means that the key and salt are likely to be leaked.

4. Optimization

The above method can achieve basic JWT authentication, but there are other problems in the actual development process:

After the token is generated, it is invalidated by expire. After the issuance of the token, it is impossible to withdraw the modification. Therefore, the change of the validity period of the token is a difficult problem, which is reflected in the following two problems:

  • Question 1. User logout
  • Question 2. Automatic token extension

How to solve the problem of changing the validity period of the token, I have seen a lot of discussions on the Internet, mainly focusing on the following:

  1. JWT is a one-time authentication that loads information into the token, and the token information contains expiration information. If the expiration time is too long, the risk of being replayed will be too great, and if the expiration time is too short, the experience of the requester will be too bad (you will have to log in again whenever you want)
  2. To store tokens in the library, it is natural to think of storing each token in the library, setting a valid field, once it is cancelled, valid=0; setting the validity field, if you want to extend it, increase the validity time. This is what openstack keystone does. Although this approach is convenient, it puts a lot of pressure on the database, and even in the case of a large number of visits and a large number of tokens issued, it is a challenge to the database. Moreover, this is also contrary to the original intention of JWT.
  3. In order to prevent the user from logging in again frequently, the client saves the user name and password (cookie), and then uses the user name and password for authentication, but the issue of defending against CSRF attacks must be considered.

Here, the author draws on the third-party authentication protocol Oauth2.0 ( RFC6749 ), which takes another approach: refresh token, a token used to update the token. After the user authenticates for the first time, two tokens are issued:

  • One is an access token, which is used for the authentication information carried in each subsequent request of the user
  • The other is refresh token, which is used to apply for a new access token after the access token expires.

In this way, two different types of tokens can be set with different validity periods. For example, access tokens are valid for only one hour, while refresh tokens can be one month. The logout of the api is achieved through the expiration of the access token (the front end can directly discard this token to log out). During the lifetime of the refresh token, you can execute the refresh token to apply for a new access token when accessing the api (the front end can save this refresh) The token and access token are updated to achieve the effect of automatic extension). Refresh token cannot be renewed any longer. After expiration, you need to log in with your username and password again.

The idea of ​​this approach is to divide the certificate into three levels:

  • access token short-term certificate, used for final authentication
  • refresh token is a longer-term certificate, used to generate a short-term certificate, not directly used for service requests
  • The user name and password are almost permanent certificates, used to generate long-term certificates and short-term certificates, and cannot be directly used for service requests

In this way, the validity of the certificate and the validity of the certificate are considered together. ps. As mentioned earlier, when creating a token, expire_in (recommended field of jwt, timeout interval) is used as a formal parameter of the function, in order to use this function to generate access token and refresh token, and the expire_in time of the two is different .

5. Summary

We made a JWT authentication module: (access token is'token' in the following code, refresh token is'rftoken' in the code)

  • First certification

client --user name password---> server client <--token, rftoken-- server

  • Requests during the lifetime of the access token

client --request (carrying token) --> server client <---result----server

  • access token timeout

client ---request (with token) --> server client <----msg:token expired-- server

  • Re-apply for access token

client-request new token (carry rftoken) -> server client <-----new token----- server

  • rftoken token timeout

client-request a new token (carry rftoken) -> server client <----msg:rftoken expired--- server

If you design a front end for this certification, you need:

  • Store access token, refresh token
  • The access token is carried during access, and the access token is automatically checked for timeout, and refresh token is used to update the access token when the timeout expires; users will not perceive the status extension
  • The user logs out and discards the access token and refresh token directly
Reference: https://cloud.tencent.com/developer/article/1033469 Use python to implement JWT authentication of the back-end system-Cloud + Community-Tencent Cloud