JSON Web Token (JWT) is currently one of the most popular cross-domain authentication solutions. Today we will uncover its mystery!
1. The origin of the story
Speaking of JWT, let's talk about the traditional session
authentication-based solutions and bottlenecks.
The traditional session
interaction process is as follows:
When the browser sends a login request to the server, after the authentication is passed, the user information seesion
will be stored in, and then the server will generate one and sessionId
put it cookie
in, and then return it to the browser.
When the browser sends the request again, it will cookie
put it in the request header sessionId
and send the request data to the server.
The server can seesion
obtain user information again , and the whole process is complete!
Usually at the server will set the seesion
duration, such as 30 minutes of inactivity, the user will have to store information from the seesion
removal of the.
session.setMaxInactiveInterval(30 * 60);//30分钟没活动,自动移除
Meanwhile, the server can also seesion
be determined whether the current user has logged in, if blank indicates no log, a direct jump to the login page; if not empty, from the session
acquired information, the user can proceed.
In a monolithic application, this kind of interaction is fine.
However, if the request volume of the application server becomes very large, and the request volume that a single server can support is limited, it is easy to slow down or slow down the request at this time OOM
.
The solution is to either add configuration to a single server, or add a new server, to meet business needs through load balancing.
If it is to add configuration to a single server, the request volume will continue to increase, and it will still not be able to support business processing.
Obviously, adding new servers can achieve unlimited horizontal expansion.
However, after adding a new server, the difference between different servers sessionId
may be A
successful . You may have successfully logged in on the server, and you can session
get user information from the B
server, but you can't find the session
information on the server. This is definitely incomparable. Awkwardly, I had to log out to continue logging in. As a result, the A
server session
failed to time out and was forced to log out to request to log in again after logging in. It was embarrassing to think about it~~
Faced with this situation, several bigwigs discussed together and came up with a token
plan.
Connect each application to the in-memory database redis
, and perform a certain algorithm encryption on the user information that has successfully logged in. The generated ID
is called token
, token
and the user's information will be stored redis
; when the user initiates the request again, token
there will be the request data one. And send it to the server. The server verifies token
whether it exists redis
. If it exists, it means the verification is passed. If it does not exist, it tells the browser to jump to the login page and the process ends.
token
The solution ensures that the service is stateless, and all information is stored in the distributed cache. Based on distributed storage, it can scale horizontally to support high concurrency.
Of course, there is springboot
also a session
sharing scheme now , and similar token
schemes will be session
stored redis
in it. After a login is implemented in a cluster environment, each server can obtain user information.
Second, what is JWT
Above, we talked about session
other token
solutions. In a cluster environment, they all rely on a third-party cache database redis
to share data.
Is there a solution that does not need to cache the database redis
to realize the sharing of user information, so as to achieve the effect that can be seen everywhere in one login?
The answer is definitely there, which is what we are going to introduce today JWT
!
JWT
In full JSON Web Token
, the implementation process simply means that after the user logs in successfully, the user's information is encrypted, and then one is generated and token
returned to the client, which is session
not much different from traditional interaction.
The interaction process is as follows:
The only difference is that : token
to store the user's basic information, more intuitive point is originally placed redis
in the user data, to put token
in the go!
As a result, the client, the server can be from token
access to basic information of the user, since the client can get, certainly not to store sensitive information , because the browser directly from the token
get user information.
What exactly does JWT look like?
JWT is composed of three pieces of information, and these three pieces of information text are .
linked together to form a JWT
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 are generally fixed;
-
The second part: We call it the payload, and the user data is stored in it;
-
The third part: is the visa (signature), mainly used for server verification;
1、header
The header of the JWT carries two parts of information:
-
Declaration type, here is JWT;
-
Declare the encryption algorithm, usually HMAC SHA256 directly;
The complete header looks like the following JSON:
{
'typ': 'JWT',
'alg': 'HS256'
}
The use of base64
encryption constitutes the first part.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2、playload
The payload is where the valid information is stored. The valid information consists of three parts:
-
Declaration registered in the standard;
-
Public statement
-
Private statement
Among them, the statement registered in the standard (recommended but not mandatory) includes the following parts :
-
iss: jwt issuer;
-
sub: the user that jwt is facing;
-
aud: the party receiving jwt;
-
exp: the expiration time of jwt, this expiration time must be greater than the issuance time;
-
nbf: Define the time before the jwt is unavailable;
-
iat: the issuance time of jwt;
-
The unique identity of jwt is mainly used as a one-time token to avoid replay attacks;
Public statement part : Any information can be added to the public statement, generally adding user-related information or other necessary information required by the business, but it is not recommended to add sensitive information, because this part can be decrypted on the client.
Private declaration part : A private declaration is a declaration jointly defined by providers and consumers. It is generally not recommended to store sensitive information, because it base64
is symmetrically decrypted, which means that this part of information can be classified as plaintext information.
Define a payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Then base64
encrypt it to get Jwt
the second part:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3、signature
The third part of jwt is a visa information, this visa information consists of three parts:
-
header (after base64);
-
payload (after base64);
-
secret (key);
This part needs to be base64
encrypted header
and base64
encrypted payload
using .
the string composed of the connection, and then through header
the encryption method declared in the salt secret
combination encryption, and then constitute the jwt
third part.
//javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, '密钥');
After encryption, the signature
signature information is obtained .
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
.
Connect these three parts into a complete string to form the final jwt:
//jwt最终格式
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
This is just javascript
a demonstration through implementation, JWT
the issuance and key storage are all done on the server side.
secret
It is used for jwt
issuance and jwt
verification, so it should not be revealed in any scene .
Three, actual combat
I have introduced so much, how to achieve it? Don't talk nonsense, let's start directly below!
-
Create a
springboot
project and addJWT
dependent libraries
<!-- jwt支持 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
-
Then, create a class of user information will be stored in an encrypted by
token
the
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserToken implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private String userId;
/**
* 用户登录账户
*/
private String userNo;
/**
* 用户中文名
*/
private String userName;
}
-
Next, create a
JwtTokenUtil
tool class to createtoken
and verifytoken
public class JwtTokenUtil {
//定义token返回头部
public static final String AUTH_HEADER_KEY = "Authorization";
//token前缀
public static final String TOKEN_PREFIX = "Bearer ";
//签名密钥
public static final String KEY = "q3t6w9z$C&F)J@NcQfTjWnZr4u7x";
//有效期默认为 2hour
public static final Long EXPIRATION_TIME = 1000L*60*60*2;
/**
* 创建TOKEN
* @param content
* @return
*/
public static String createToken(String content){
return TOKEN_PREFIX + JWT.create()
.withSubject(content)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(Algorithm.HMAC512(KEY));
}
/**
* 验证token
* @param token
*/
public static String verifyToken(String token) throws Exception {
try {
return JWT.require(Algorithm.HMAC512(KEY))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
} catch (TokenExpiredException e){
throw new Exception("token已失效,请重新登录",e);
} catch (JWTVerificationException e) {
throw new Exception("token验证失败!",e);
}
}
}
-
Write configuration classes, allow cross-domain, and create a permission interceptor
@Slf4j
@Configuration
public class GlobalWebMvcConfig implements WebMvcConfigurer {
/**
* 重写父类提供的跨域请求处理的接口
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径
registry.addMapping("/**")
// 放行哪些原始域
.allowedOrigins("*")
// 是否发送Cookie信息
.allowCredentials(true)
// 放行哪些原始域(请求方式)
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
// 放行哪些原始域(头部信息)
.allowedHeaders("*")
// 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)
.exposedHeaders("Server","Content-Length", "Authorization", "Access-Token", "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials");
}
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加权限拦截器
registry.addInterceptor( new AuthenticationInterceptor()) .addPathPatterns("/**").excludePathPatterns("/static/**");
}
}
-
Use
AuthenticationInterceptor
interceptors to verify interface parameters
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从http请求头中取出token
final String token = request.getHeader(JwtTokenUtil.AUTH_HEADER_KEY);
//如果不是映射到方法,直接通过
if(!(handler instanceof HandlerMethod)){
return true;
}
//如果是方法探测,直接通过
if (HttpMethod.OPTIONS.equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
//如果方法有JwtIgnore注解,直接通过
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method=handlerMethod.getMethod();
if (method.isAnnotationPresent(JwtIgnore.class)) {
JwtIgnore jwtIgnore = method.getAnnotation(JwtIgnore.class);
if(jwtIgnore.value()){
return true;
}
}
LocalAssert.isStringEmpty(token, "token为空,鉴权失败!");
//验证,并获取token内部信息
String userToken = JwtTokenUtil.verifyToken(token);
//将token放入本地缓存
WebContextUtil.setUserToken(userToken);
return true;
}
@Override
public void afterCompletion( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//方法结束后,移除缓存的token
WebContextUtil.removeUserToken();
}
}
-
Finally, after the
controller
layer user logs in, create onetoken
and store it in the header.
/**
* 登录
* @param userDto
* @return
*/
@JwtIgnore
@RequestMapping(value = "/login", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"})
public UserVo login(@RequestBody UserDto userDto, HttpServletResponse response){
//...参数合法性验证
//从数据库获取用户信息
User dbUser = userService.selectByUserNo(userDto.getUserNo);
//....用户、密码验证
//创建token,并将token放在响应头
UserToken userToken = new UserToken();
BeanUtils.copyProperties(dbUser,userToken);
String token = JwtTokenUtil.createToken(JSONObject.toJSONString(userToken));
response.setHeader(JwtTokenUtil.AUTH_HEADER_KEY, token);
//定义返回结果
UserVo result = new UserVo();
BeanUtils.copyProperties(dbUser,result);
return result;
}
It's basically done here!
What AuthenticationInterceptor
is used in it JwtIgnore
is an annotation, which is used for token
methods that do not require verification , such as obtaining a verification code, and so on. @Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtIgnore {
boolean value() default true;
}
And WebContextUtil
a thread cache tools, other interfaces can be from by this method token
to obtain user information.
public class WebContextUtil {
//本地线程缓存token
private static ThreadLocal<String> local = new ThreadLocal<>();
/**
* 设置token信息
* @param content
*/
public static void setUserToken(String content){
removeUserToken();
local.set(content);
}
/**
* 获取token信息
* @return
*/
public static UserToken getUserToken(){
if(local.get() != null){
UserToken userToken = JSONObject.parseObject(local.get() , UserToken.class);
return userToken;
}
return null;
}
/**
* 移除token信息
* @return
*/
public static void removeUserToken(){
if(local.get() != null){
local.remove();
}
}
}
Finally, start the project, let's postman
test it and see if the header returns the result.
We extract the returned information and use the browser base64
to decrypt the first two parts.
-
The first part, the header, the result is as follows:
-
The second part, which is the playload, the results are as follows:
It can be clearly seen that the header and payload information can be base64
decrypted.
So, be sure not token
to store sensitive information in it !
When we need to request other service interfaces, we only need headers
to add Authorization
parameters to the request header .
After the permission interceptor is verified, the WebContextUtil
user information can be obtained only through the tool class in the interface method .
//获取用户token信息
UserToken userToken = WebContextUtil.getUserToken();
Four, summary
JWT
Compared session
program, because json
of the versatility, it JWT
can be cross-language support, like JAVA
, JavaScript
, PHP
and many other languages can be used, but session
the program only for JAVA
.
Because of the payload
part, it JWT
can store some non-sensitive information necessary for other business logic.
At the same time, it is secret
very important to protect the private key of the server , because the private key can verify and decrypt the data!
If you can, please use the https
agreement!
Five, reference
1. Brief Book-What is JWT-JSON WEB TOKEN
2. Blog Garden-an identity authentication scheme based on session and token
------------------------------------------- < END >---------------------------------------
Teach you how to use JWT to achieve single sign-on
Original Duck Blood Fans Java Geek Technology May 15
https://mp.weixin.qq.com/s/3qm3RvSG_1LQ0v676XU7jA
If you like our article, you are welcome to forward it and click on it to let more people see it. We also welcome friends who love technology and learning to join our knowledge planet. We grow and progress together.