How to Use JWT to defend against CSRF
The names are all used to notify people.
The following two terms are explained: CSRF and JWT.
CSRF (Cross Site Request Forgery) indicates that you open two tabs in a browser, one of which sends forged requests by stealing cookies from the other, because the cookie is automatically sent to the server with the request.
JWT (JSON Web Token) encrypts two JSON objects into a string using an algorithm, which can represent a unique user.
Generation of CSRF
First, we can use a diagram to understand what CSRF is.
These three steps are indispensable for successful attacks.
First, log on to the victim's website. If the victim's website is a cookie-based user authentication mechanism, the browser will save the SESSIONID of the server after the user successfully logs on to the website.
Second, when the attacker's website is opened in the same browser, although it cannot obtain the SESSIONID (because the cookie with http only setting cannot be obtained by JavaScript ), however, any request sent from a browser to a victim's website carries its cookie, no matter which website it sends.
Third, using this principle, attackers can send a request to the victim's website to perform some sensitive operations. Because the request is in the session, as long as the user has the permission, any request will be executed.
For example, open Youku and log on. Then open the attacker's website, which has a tag like this:
This api is just an example. The specific url and parameters can be determined by the browser's developer tool (Network function) in advance. If the function of this program is to let the login user focus on a program or user identified by 123, then through CSRF attacks, the broadcast volume of this program will continue to rise.
Here are two explanations. First, why is this case not a bank-related operation? It is easy because it is easy to guess. For the attacker, nothing can be successful, such as SQL injection. The attacker does not know how to design the database of a website, but he usually tries it through his personal experience, for example, many websites set users' primary keys to user_id or sys_id.
Banking operations often go through multiple types of validation, such as symmetric verification codes and mobile phone verification codes. It is basically a fantasy that CSRF alone performs an attack. However, other types of websites do not prevent such problems. Although monetary benefits are difficult to obtain, CSRF can do a lot of things, such as using others to send fake Weibo posts and add friends, which can all benefit attackers.
Second, how can we ensure that users can access the attacker's website after opening Youku? You cannot. Otherwise, anyone who opens Youku will follow a program inexplicably. But you need to know that the attack cost is only an API call, and it can appear everywhere. You can download an image from anywhere and request this address, if you don't want to see it, just click OK. Isn't the request sent?
CSRF defense
There are three methods to prevent CSRF attacks.
Determine the Referer in the Request Header
This field records the request source. For example, the http://www.example.com calls the Baidu interface http://api.map.baidu.com/service so in the Baidu server, you can judge through Referer this request is from where.
In practical applications, these operations unrelated to the business logic are often placed in the Interceptor (or the filters, the terms used by different technologies may be different ). That is to say, before entering the business logic, we should determine whether the request can be processed based on the Referer value.
In Java Servlet, Filter can be used (the old technology); in Spring, interceptor can be built; In Express, it is called middleware through request. get ('Referer') to get this value. Each Technology follows the same process.
However, it should be noted that Referer is set by the browser. In the era of high browser compatibility, if a Browser allows users to modify this value, the CSRF vulnerability still exists.
Add csrf token to Request Parameters
There is no need to prevent GET requests and POST requests. Why? Because GET is considered as a query operation in the "Conventions", the query means that you query it once, twice, and countless times, the results will not change (the data obtained by the user may change), which will not affect the database, so no additional parameters need to be added.
Therefore, we would like to remind you that you should try to follow these conventions and do not use the/delete,/update,/edit words in GET requests. Put the "write" Operation in POST.
For POST, the server can add a hidden field when creating a form, which is also obtained through an encryption algorithm. When processing the request, verify that this field is valid. If it is valid, continue to process it; otherwise, it is regarded as a malicious operation.
<Form method = "post" action = "/delete"> <! -- Other fields --> <input type = "hidden" name = "csrftoken" value = "generated by the server"/> </form>
This html segment is generated by the server, such as JSP and PHP. For Node. js, it can be Jade.
This is indeed a good precaution. Adding more processing measures can also prevent repeated submission of forms.
However, for some emerging websites, many adopt the "single page" design, or take a step back. Whether it is a single page or not, its HTML may be spliced by JavaScript, and forms are also submitted asynchronously. Therefore, this method has its application scenarios and limitations.
Add HTTP Header
The idea is to put the token in the request header. The server can obtain the request header like a Referer. The difference is that the token is generated by the server, so attackers cannot guess it.
Another focus of this article, JWT, is based on this method. Aside from JWT, it works like this:
The four requests are of the POST type.
Log on to the server through the/login interface and return an access_token. The front-end stores the token in the memory if you want to simulate the session. You can also save it to localStorage To Enable Automatic Logon.
Call the/delete interface. The parameter is the id of a product. In this request, a header named Authoriaztion is added, and its value is the access_token that was previously passed back from the server, A "Bearer" is added before (this is an agreement with the server, that is, if it is said that the addition is done, the addition is done together, and the addition is not done ......)
Call the/logout interface and add the access_token to the header. After the token is successfully deleted, both the server and the front-end disable the token or delete it directly.
Call the/delete interface again. Because no access_token is available at this time, the server determines that the request has no permission and returns 401.
Have you found that, from the beginning to the end, the entire process does not involve cookies, so CSRF is impossible!
JWT conventions
If you don't care about JWT, you can end the article, because here, apart from the content mentioned in the chapter title, you can also come up with the following points: first, consider more when designing an API; second, use token for single-point logon. Third, the two user authentication mechanisms cookie and token are different.
JWT is actually the new HTTP Header convention. For example, the parameters in the GET request are defined to be separated by &, but can they be used with other parameters? Of course you can. You can also use a comma or semicolon. The server just needs to specify an escape rule. However, conventions are intended to allow everyone to do things in a more standardized manner. If they follow the conventions, there will be little code to change from one tool to another. I will not go into detail here.
Three Components
This website provides the most official description of JWT terms and content.
Each part of JWT is a string separated by vertices, so its format is as follows:
XXX1.XXX2.XXX3
The entire string is URL-safe, so it can be directly used in GET request parameters.
Part 1 JWT Header
It is a JSON object that represents the type and encryption algorithm of the entire string, such
{ "typ":"JWT", "alg":"HS256"}
After base64url encryption
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
Part 2 JWT Claims Set
It is also a JSON object that uniquely represents a user, such
{ "iss": "123", "exp": 1441593850}
After base64url encryption
eyJpc3MiOiIxMjMiLCJleHAiOjE0NDE1OTM4NTB9
If you have detailed attribute descriptions on the official website, try to use the Registered Claim Names mentioned in it to improve readability. Here, iss indicates issuer, which is the person who initiates the request. Its value is related to the business, so it is determined by your application. Exp indicates the expiration time, that is, when it expires. Note that this value is in seconds rather than milliseconds, so it is within the Integer Range.
Part 3: JWS Signature
The signature is calculated based on the alg attribute in the first part. If it is HS256, the server needs to save a private key, such as secret. Then, connect the two strings generated in part 1 and part 2 with a dot and then use the private key. Then, use HS256 encryption to obtain the following string:
AOtbon6CebgO4WO9iJ4r6ASUl1pACYUetSIww-GQ72w
Now we have collected three parts and connected them with. To get the complete token.
Example 1/2: Use Express as the server
For the server, various libraries already exist to support JWT. The following are recommended:
Platform |
Library |
Java |
Maven com. auth0/java-jwt/0.4 |
PHP |
Composer require lcobucci/jwt |
Ruby |
Gem install jwt |
. NET |
Install-Package System. IdentityModel. Tokens. Jwt |
Node. js |
Npm install jsonwebtoken |
If you have learned Node. js and Express before, you can easily understand the following code.
Var router = express. router (), PRIVATE_KEY = 'secret'; router. post ('/login', function (req, res, next) {// generate JWT var token = jwt. sign ({iss: '000000'}, PRIVATE_KEY, {expiresInMinutes: 60}); // return JWT to the front-end res. send ({access_token: token}) ;}); router. post ('/delete', function (req, res, next) {var auth = req. get ('authorization'), token = null; // determines whether the request header contains the Authoriaztion field. In order to shorten the Code, other verification if (auth) is reduced) {token =/Bearer (. + )/. exec (auth) [1]; res. send (jwt. decode (token);} else {res. sendStatus (401 );}});
For the use of jsonwebtoken, see its manual.
Two APIs are defined in the example.
/Login, a jwt string is returned. It contains a user id and survival time, which will be converted to exp and iat (issue at, request initiation time). The difference between the two is the survival time.
/Delete: verify whether the Authorization field exists in the request header and is valid. If yes, the request is processed; otherwise, 401 is returned.
Note that the Authoriaztion request header that the server expects is in the following format:
Authorization: Bearer XXX1.XXX2.XXX3
This is not related to JWT and is a format of OAuth 2.0. Because the Authorization field is also agreed, it consists of the token type and value, the type in addition to the aforementioned Bearer, as well as Basic, MAC and so on.
Example 2/2: Use Backbone as the frontend
The front-end is divided into two aspects: one is to store jwt, and the other is to add Authoriaztion to all request headers.
If you refactor the existing code, the second job may be a little difficult, unless the forms in the old Code are submitted asynchronously and the request method is self-packaged, this is the only way to modify the request header.
In this article a few weeks ago, I wrote how to intercept requests in Angular. Now we will take Backbone as an example.
// First save the original sync method var sync = Backbone. sync; Backbone. sync = function (method, model, options) {var token = window. localStorage. getItem ('jwt '); // if a token exists, add it to the Request Header if (token) {options. headers. authorization = 'bearer' + token;} // call the original sync method sync (method, model, options );};
Additional cross-origin Processing
In cross-origin application scenarios, the server needs to make some additional settings that are added to the response header.
Access-Control-Allow-Origin: *Access-Control-Allow-Headers: Authorization
The first one allows requests from any domain name. The second parameter indicates that some custom request headers are allowed. Because Authoriaztion is customized, this configuration must be added. If you use other request headers, add them as well.
If the server uses nginx, these configurations can be written in the nginx. conf file. If it is configured in the Code, both Java and Node. js have the response. setHeader method.
Summary
I don't know much about Web security, so I don't have much experience to talk about. Security is a field that is rarely valued, because the priority of a project is always: function> face filter> performance and security. At least ensure that the user will not make any mistakes during use, and then make it cool or fresh. performance and security should be considered only when the first two items are satisfied or urgent. When the server cannot afford such high load, it will add more servers, but the business functions should not be less from the very beginning.
But is this wrong? No. In specific scenarios, specific processing may be the most cost-effective decision.
The term "agreement" is repeatedly mentioned in this article. It seems to be in conflict with the viewpoint of "specific analysis of specific situations .......
Conventions are consensus between people, such as GET requests. The first response of the other party is query. When someone breaks the conventions and uses GET requests for deletion, it will make it hard for others to understand (when there are a lot of people doing this, it is not hard to understand ......). Or when we mention JWT, it should be composed of three parts. If someone just generates a token based on their own algorithms, it can also uniquely identify the user, then he must explain the security and usage of the algorithm as he worked.
On the other hand, if you really feel that it is unnecessary to follow the "Conventions", it is too troublesome, And you can accept the consequences of "being smart, let's do it as you wish (Do you really stop thinking about it ).
Why does HTML5 add so many semantic tags because everything is moving in a more standard direction.