JWT (English full name: JSON Web Token) is currently one of the most popular cross-domain authentication solutions, today we unveil its mysterious veil!
I. Origin of the story
When it comes to JWT, let's first talk about traditionalsession
certified programs as well as bottlenecks.
legacysession
The interaction flow, as shown below:
When the browser sends a login request to the server, after it passes authentication, it stores the user's information into theseesion
in the server, and then the server generates asessionId
put intocookie
which is then returned to the browser.
When the browser sends the request again, thecookie
put intosessionId
, sends the request data to the server together.
The server will then be able to get the information again from theseesion
Get user information and the whole process is complete!
Typically on the server side you would set theseesion
If the user has been inactive for, for example, 30 minutes, the stored user information will be removed from theseesion
Remove.
(30 * 60);//30 minutes of inactivity, automatically removed
Also, on the server side theseesion
to determine whether the current user is logged in, if it is empty means not logged in, directly jump to the login page; if it is not empty, you can start from thesession
The user's information can be retrieved from the
In a monolithic application, there is nothing wrong with such an interaction.
However, if the volume of requests to the application server becomes very large, and there is a limit to the number of requests that a single server can support, it is easy for requests to slow down or toOOM
。
The solution is to either add configuration to a single server or add new servers to meet the needs of the business through load balancing.
If the configuration is added to a single server, the volume of requests continues to get larger and still cannot support business processing.
Obviously, adding new servers allows for unlimited horizontal scaling.
But after adding new servers, the different servers between thesessionId
is not the same as it might be inA
The server has been logged in successfully and can be accessed from the server'ssession
to get user information in theB
I can't find it on the server.session
message, which must have been incredibly embarrassing at this point, so I had to log out and keep logging in, and I ended upA
in the serversession
It's embarrassing to think that after logging in, you're forced to log out and ask to log in again because the timeout expires~~
Faced with this situation, a few bigwigs then combined their deliberations and came up with atoken
Programs.
Connecting individual applications to the in-memory databaseredis
connected, the user information of successful login is encrypted with certain algorithms, and the generatedID
be known astoken
willtoken
And the user's information is depositedredis
; wait for the user to initiate the request again by placing thetoken
The request data is also sent to the server, and the server verifies it.token
whether or notredis
If it exists, the authentication passes, and if it doesn't, the browser is told to jump to the login page and the process ends.
token
The solution ensures that the service is stateless and all the information is present in the distributed cache. Based on distributed storage, this can be scaled horizontally to support high concurrency.
Sure. Now.springboot
Also availablesession
Shared programs, similartoken
The program willsession
deposit intoredis
In the cluster environment, after a single login, each server can obtain the user information.
Second, what is JWT
Above, we talked about thesession
there aretoken
programs, in a clustered environment, they all rely on third-party caching databasesredis
to enable data sharing.
So is there a program that doesn't use a cached databaseredis
What about sharing user information to achieve the effect of logging in once and seeing it everywhere?
The answer is definitely there, and it's the one we're going to cover todayJWT
!
JWT
full nameJSON Web Token
The implementation process is simply to encrypt the user's information after the user has successfully logged in, and then generate atoken
returned to the client, as opposed to the traditionalsession
The interactions aren't much different.
The interaction flow is as follows:
The only difference is that:token
stores basic information about the user, or more intuitively, the information that would otherwise go into theredis
in the user data, put it into thetoken
Going in!
In this way, the client, and the server can get the information from thetoken
to get basic information about the user, and since the client can get it, theI'm sure you can't store sensitive information., because browsers can directly retrieve the data from thetoken
Get user information.
What does JWT look like exactly?
The JWT is made up of three pieces of information, and the text of these three pieces of information is summarized using the.
The links together make up theJWT
String. Like this.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
- The first part: we call it the header, which is used to store the token type and encryption protocol, which is usually fixed;
- The second part: we call it the payload, in which the user data is stored;
- The third part: is the visa (signature), mainly used for server-side authentication;
1、header
The header of a JWT carries two parts of information:
- Declare the type, in this case JWT;
- Declare the algorithm for encryption, usually directly using HMAC SHA256;
The full header looks like the following JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
utilizationbase64
encryption, constitutes the first part.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2、playload
The load is where the valid information is stored, and this valid information consists of three parts:
- statement registered in the standard;
- Public statement;
- Private statement;
Among other things, the statement of registration in the standard (which is recommended but not mandatory) includes the following sections :
- iss: jwt issuer;
- sub: The user for which the jwt is intended;
- aud: The party receiving the jwt;
- exp: the expiration time of the jwt, this expiration time must be greater than the issuance time;
- nbf: defines until what time this jwt is unavailable;
- iat: The issuance time of the jwt;
- jwt is a unique identifier, mainly used as a one-time token, thus avoiding replay attacks;
The public part of the statement:
The public statement can add any information, generally add the user's relevant information or other necessary information for business needs, but it is not recommended to add sensitive information, because the part can be decrypted on the client side.
Private declaration section:
A private statement is a statement defined jointly by the provider and the consumer, and is generally not recommended for storing sensitive information, as thebase64
is symmetrically decrypted, meaning that that part of the message can be categorized as plaintext.
Define a payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
It is then subjected tobase64
Encrypt it to getJwt
The second part of the
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3、signature
The third part of the jwt is a visa information which consists of three parts:
- header (after base64);
- payload (after base64);
- secret (key).
This section requiresbase64
encryptedheader
cap (a poem)base64
encryptedpayload
utilization.
concatenates the constituent strings, which are then passed through theheader
Salt the encryption declared insecret
Combined encryption then constitutes thejwt
The third part of the
//javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'keys');
After encryption, you getsignature
Signature information.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Combine these three parts with.
Concatenated into a complete string, this constitutes the final jwt:
//jwt final format
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
This one just goes throughjavascript
realization of a demo thatJWT
The issuing and saving of keys is done on the server side.
secret
gojwt
Issuance andjwt
of validation, so it should not flow out in any scenario。
III. Actual combat
Introduced so much, how to realize it? Without further ado, here we go directly to jack!
- Create a
springboot
project, add theJWT
dependency library (computing)
<!-- jwtbe in favor of -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
- Then, create a user information class that will be encrypted and stored in the
token
center
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserToken implements Serializable {
private static final long serialVersionUID = 1L;
/**
* subscribersID
*/
private String userId;
/**
* subscribers登录账户
*/
private String userNo;
/**
* subscribers中文名
*/
private String userName;
}
- Next, create a
JwtTokenUtil
Tool class for creatingtoken
Verificationtoken
public class JwtTokenUtil {
//definetokenReturn to header
public static final String AUTH_HEADER_KEY = "Authorization";
//tokenprefix (linguistics)
public static final String TOKEN_PREFIX = "Bearer ";
//signing key
public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
//The expiration date defaults to 2hour
public static final Long EXPIRATION_TIME = 1000L*60*60*2;
/**
* establishTOKEN
* @param content
* @return
*/
public static String createToken(String content){
return TOKEN_PREFIX + ()
.withSubject(content)
.withExpiresAt(new Date(() + EXPIRATION_TIME))
.sign(Algorithm.HMAC512(KEY));
}
/**
* validate (a theory)token
* @param token
*/
public static String verifyToken(String token) throws Exception {
try {
return (Algorithm.HMAC512(KEY))
.build()
.verify((TOKEN_PREFIX, ""))
.getSubject();
} catch (TokenExpiredException e){
throw new Exception("tokenexpired,Please log in again",e);
} catch (JWTVerificationException e) {
throw new Exception("tokenvalidate (a theory)失败!",e);
}
}
}
- Write a configuration class to allow cross-domain and create a permission interceptor
@Slf4j
@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
/**
* Overrides the interface provided by the parent class for handling cross-domain requests.
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// Add the mapping path
("/**")
// Which raw fields to release
.allowedOrigins("*")
// Whether to send cookie information
.allowCredentials(true)
// Which raw domains to let go of (request methods)
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
// Which raw fields to allow (headers)
.allowedHeaders("*")
// which headers to expose (since cross-domain access doesn't get all headers by default)
.exposedHeaders("Server", "Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin", "Access-Control-Allow- Credentials");
}
/**
* Add interceptor
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Add a privilege interceptor
(new AuthenticationInterceptor()).addPathPatterns("/**").excludePathPatterns("/static/**");
}
}
- utilization
AuthenticationInterceptor
Interceptor validation against interface parameters
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// through (a gap)httpin the request headertoken
final String token = (JwtTokenUtil.AUTH_HEADER_KEY);
//If not mapped to a method,Directly through
if(!(handler instanceof HandlerMethod)){
return true;
}
//If the method detects,Directly through
if ((())) {
(HttpServletResponse.SC_OK);
return true;
}
//If the method hasJwtIgnoreexplanatory note,Directly through
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method=();
if (()) {
JwtIgnore jwtIgnore = ();
if(()){
return true;
}
}
(token, "tokenempty,authentication failure!");
//validate (a theory),and gettokenInternal information
String userToken = (token);
//commander-in-chief (military)tokenlocal cache
(userToken);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//At the end of the method,Remove the cachedtoken
();
}
}
- Finally, in the
controller
After the layer user logs in, create atoken
Just store it in the head.
/**
* log in
* @param userDto
* @return
*/
@JwtIgnore
@RequestMapping(value = "/login", method = , produces = {"application/json;charset=UTF-8"})
public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
//...Parameter legitimacy verification
//Getting user information from the database
User dbUser = ();
//....subscribers、password verification
//establishtoken,includetokenPut it in the response header
UserToken userToken = new UserToken();
(dbUser,userToken);
String token = ((userToken));
(JwtTokenUtil.AUTH_HEADER_KEY, token);
//Define the return result
UserVo result = new UserVo();
(dbUser,result);
return result;
}
It's basically done at this point!
included among theseAuthenticationInterceptor
inJwtIgnore
is an annotation for use without validationtoken
on methods such as CAPTCHA fetching and so on.
@Target({, })
@Retention()
public @interface JwtIgnore {
boolean value() default true;
}
(indicates contrast)WebContextUtil
is a thread caching utility class through which other interfaces can be accessed from thetoken
Get user information in the
public class WebContextUtil {
//Local thread cachingtoken
private static ThreadLocal<String> local = new ThreadLocal<>();
/**
* set uptokentext
* @param content
*/
public static void setUserToken(String content){
removeUserToken();
(content);
}
/**
* gaintokentext
* @return
*/
public static UserToken getUserToken(){
if(() != null){
UserToken userToken = (() , );
return userToken;
}
return null;
}
/**
* removestokentext
* @return
*/
public static void removeUserToken(){
if(() != null){
();
}
}
}
Finally, to start the project, let's use thepostman
Test it and see what the header returns.
We extract the returned information and process it, using the browser'sbase64
Decrypt the first two parts.
- The first part, the header, results in the following:
- The second part, which is playload, results in the following:
It can be clearly seen that the header, load information can be passed through thebase64
Decrypt it out.
So, make sure you don't get intoken
Storing sensitive information in!
When we need to request other service interfaces, we just need to add the following to the request headerheaders
add inAuthorization
parameter is sufficient.
Once the privilege interceptor has been validated, the interface method only needs to be validated by theWebContextUtil
The tool class will be able to get user information.
// Get user token information
UserToken userToken = ();
IV. Summary
JWT
comparesession
program becausejson
The generalization of theJWT
It is possible to have cross-language support, likeJAVA
、JavaScript
、PHP
and many other languages are available, and thesession
Programs are only forJAVA
。
Because of thepayload
part, soJWT
Some non-sensitive information necessary for other business logic can be stored.
In the meantime, protect the server sidesecret
The private key is very important because the private key can validate and decrypt the data. If you can, use thehttps
Agreement!
The project source code is located at the following address!
/pzblogs/spring-boot-example-demo
V. References
1、Jane's Book - What is JWT -- JSON WEB TOKEN
2、Blogspot - Session and token based authentication scheme