In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-22 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article introduces the knowledge of "how to use JWT to achieve single sign-on". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!
First, the origin of the story
Speaking of JWT, let's first talk about the scheme and bottleneck based on traditional session authentication.
Traditional session interaction process, as shown below:
When the browser sends a login request to the server, after the authentication is passed, the user information is stored in the seesion, and then the server generates a sessionId to put in the cookie and then returns it to the browser.
When the browser sends the request again, it puts the sessionId in the cookie of the request header and sends the request data to the server.
The server can get the user information from seesion again, and the whole process is over!
The duration of the seesion is usually set on the server, for example, if there is no activity for 30 minutes, the stored user information will be removed from the seesion.
Session.setMaxInactiveInterval (30 * 60); / / No activity for 30 minutes, automatically removed
At the same time, you can also use seesion on the server to determine whether the current user has logged in. If it is empty, you can jump to the login page directly. If it is not empty, you can obtain user information from session for follow-up operations.
In monomer applications, there is nothing wrong with this way of interaction.
However, if the number of requests from the application server becomes large, and the number of requests that a single server can support is limited, it is easy to cause requests to slow down or OOM.
The solution is to either add configuration to a single server or add a new server to meet the needs of the business through load balancing.
If the configuration is added to a single server, the number of requests continues to increase, which is still unable to support business processing.
It is obvious that the addition of new servers can achieve unlimited horizontal scale.
However, after adding a new server, the sessionId between different servers is different. You may have logged in successfully on server A, and you can get user information from server session, but you can't find session information on server B. it must be extremely embarrassing at this time, so you have to back out and continue to log in. As a result, the session in server An expires because of timeout, and is forced to log out again after login. It's awkward to think about it.
In the face of this situation, several bosses then got together to discuss and came up with a token plan.
Connect each application to the in-memory database redis, and encrypt the successful login user information by a certain algorithm. The generated ID is called token, and the token and user information are stored in redis. When the user initiates the request again, send the token and the request data to the server, and the server verifies whether the token exists in the redis. If so, the verification is passed. If it does not exist, tell the browser to jump to the login page and the process ends.
The token scheme ensures that the service is stateless and all the information is stored in the distributed cache. Based on distributed storage, it can scale horizontally to support high concurrency.
Of course, now springboot also provides session sharing scheme, which is similar to token scheme to store session into redis. After a login in a cluster environment, each server can get user information.
What is JWT?
Above, we mentioned the session and token solutions, in the cluster environment, they all rely on the third-party cache database redis to achieve data sharing.
Is there a way to share user information without caching the database redis, so as to achieve the effect that one login can be seen everywhere?
The answer must be yes, it is the JWT we are going to introduce today!
The full name of JWT is JSON Web Token, which simply means that after the user has successfully logged in, the user's information is encrypted, and then a token is generated and returned to the client, which is not much different from the traditional session interaction.
The interaction process is as follows:
The only difference is that token stores the basic information of users, and it is more intuitive that the user data that was originally put into redis is put into token!
In this way, both the client and the server can obtain the basic information of the user from the token. Since the client can access it, it must not store the sensitive information, because the browser can obtain the user information directly from the token.
What exactly does JWT look like?
JWT is composed of three pieces of information, which are used in the text. The links together make up the JWT string. It's like this:
EyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
The first part: we call it header, which is used to store token types and encryption protocols, which are generally fixed.
The second part: we call it payload, and user data is stored in it.
The third part: visa (signature), which is mainly used for server verification.
1 、 header
The header of JWT carries two pieces of information:
Declare the type, this is JWT
Declare the encryption algorithm, usually using HMAC SHA256 directly
The complete head looks like the following JSON:
{'typ':' JWT', 'alg':' HS256'}
Use base64 encryption to make up the first part.
EyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2 、 playload
The load is the place where valid information is stored, which consists of three parts:
Declaration of registration in the standard
A public statement
Private declaration
Among them, the declaration registered in the standard (recommended but not mandatory) includes the following parts:
Iss: issuer of jwt
Sub: the user for which jwt is targeted
Aud: the party that receives the jwt
Exp: the expiration time of jwt, which must be greater than the issuing time
Nbf: defines when the jwt is not available
Iat: the issuing time of the jwt
Unique identity of jwt, mainly used as an one-time token to avoid replay attacks
Public statement part: public declaration can add any information, generally add user-related information or other necessary information needed by the business, but it is not recommended to add sensitive information, because this part can be decrypted on the client.
Private declaration: a private declaration is a declaration defined by both providers and consumers. It is generally not recommended to store sensitive information, because base64 is symmetrically decrypted, which means that this part of the information can be classified as plaintext information.
Define a payload:
{"sub": "1234567890", "name": "John Doe", "admin": true}
Then encrypt it with base64 to get the second part of the Jwt:
EyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3 、 signature
The third part of jwt is a visa information, which consists of three parts:
Header (after base64)
Payload (after base64)
Secret (key)
This part requires the use of base64 encrypted header and base64 encrypted payload. Concatenate the string, then add salt secret combination encryption through the encryption declared in header, and then form the third part of the jwt.
/ / javascript var encodedString = base64UrlEncode (header) +'.'+ base64UrlEncode (payload); var signature = HMACSHA256 (encodedString, 'key')
After encryption, the signature signature information is obtained.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Use these three parts. Concatenation into a complete string forms the final jwt:
/ / jwt final format eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
This is just a demonstration implemented by javascript. The signing of JWT and the storage of keys are done on the server side.
Secret is used for jwt signing and jwt verification, so it should not be exposed in any scenario.
III. Actual combat
With so many introductions, how can it be realized? Don't talk too much nonsense, let's just do it!
Create a springboot project and add JWT dependency libraries
Com.auth0 java-jwt 3.4.0
Then, create a user information class that will be encrypted and stored in token
@ Data @ EqualsAndHashCode (callSuper = false) @ Accessors (chain = true) public class UserToken implements Serializable {private static final long serialVersionUID = 1L; / * * user ID * / private String userId; / * * user login account * / private String userNo; / * * user Chinese name * / private String userName;}
Next, create a JwtTokenUtil utility class for creating token and validating token
Public class JwtTokenUtil {/ / define token return header public static final String AUTH_HEADER_KEY = "Authorization"; / / token prefix public static final String TOKEN_PREFIX = "Bearer"; / / signature key public static final String KEY = "q3t6w9z$C&F) J@NcQfTjWnZr4u7x"; / / validity defaults to 2hour public static final Long EXPIRATION_TIME = 1000L*60*60*2 / * create 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)) } / * verify 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 is invalid, please log in again", e);} catch (JWTVerificationException e) {throw new Exception ("token authentication failed!" , e);}
Write configuration classes, allow cross-domain, and create a permission interceptor
@ Slf4j @ Configuration public class GlobalWebMvcConfig implements WebMvcConfigurer {/ * override the interface for handling cross-domain requests provided by the parent class * @ param registry * / @ Override public void addCorsMappings (CorsRegistry registry) {/ / add the mapping path registry.addMapping ("/ * *") / / which original domains are released .allowedOrigins ("*") / / whether to send Cookie information. AllowCredentials (true) / / which original domains are released (request method) .allowedMethods ("GET") "POST", "DELETE", "PUT", "OPTIONS", "HEAD") / / release which original domains (header information) .allowedHeaders ("*") / / which headers are exposed (because cross-domain access cannot get all header information 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 permission interceptor registry.addInterceptor (new AuthenticationInterceptor ()) .addPathPatterns ("/ *") .excludePathPatterns ("/ static/**");}}
Use AuthenticationInterceptor interceptor to validate interface parameters
@ Slf4j public class AuthenticationInterceptor implements HandlerInterceptor {@ Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {/ / extract token final String token = request.getHeader (JwtTokenUtil.AUTH_HEADER_KEY) from the http request header; / / if it is not mapped to a method, directly through if (! (handler instanceof HandlerMethod)) {return true } / / if it is a method detection, directly through if (HttpMethod.OPTIONS.equals (request.getMethod () {response.setStatus (HttpServletResponse.SC_OK); return true;} / / if the method has JwtIgnore annotations, directly through 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 is empty, authentication failed!") ; / / verify and get the token internal information String userToken = JwtTokenUtil.verifyToken (token); / / put the token into the local cache WebContextUtil.setUserToken (userToken); return true;} @ Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {/ / method, remove the cached token WebContextUtil.removeUserToken () }}
Finally, after the controller layer user logs in, create a token and store it in the header
/ * Login * @ param userDto * @ return * / @ JwtIgnore @ RequestMapping (value = "/ login", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) public UserVo login (@ RequestBody UserDto userDto, HttpServletResponse response) {/ /. Parameter validity verification / / get user information from database User dbUser = userService.selectByUserNo (userDto.getUserNo); / /.... User, password authentication / / create token and place token in the response header UserToken userToken = new UserToken (); BeanUtils.copyProperties (dbUser,userToken); String token = JwtTokenUtil.createToken (JSONObject.toJSONString (userToken)); response.setHeader (JwtTokenUtil.AUTH_HEADER_KEY, token); / / define the return result UserVo result = new UserVo (); BeanUtils.copyProperties (dbUser,result); return result;}
It's almost done here!
The JwtIgnore used in AuthenticationInterceptor is an annotation for methods that do not require verification of token, such as the acquisition of CAPTCHA, and so on.
@ Target ({ElementType.METHOD, ElementType.TYPE}) @ Retention (RetentionPolicy.RUNTIME) public @ interface JwtIgnore {boolean value () default true;}
WebContextUtil is a thread caching utility class, and other interfaces can obtain user information from token through this method.
Public class WebContextUtil {/ / Local thread cache token private static ThreadLocal local = new ThreadLocal (); / * set token information * @ param content * / public static void setUserToken (String content) {removeUserToken (); local.set (content) } / * get token information * @ return * / public static UserToken getUserToken () {if (local.get ()! = null) {UserToken userToken = JSONObject.parseObject (local.get (), UserToken.class); return userToken;} return null } / * * remove token message * @ return * / public static void removeUserToken () {if (local.get ()! = null) {local.remove ();}
Finally, to start the project, let's test it with postman and see the result returned by the header.
We extract the returned information and decrypt the first two parts using the browser's base64.
The first part, header, results as follows:
The second part, playload, results as follows:
It can be clearly seen that the information of the head and load can be decrypted by base64.
So, don't store sensitive information in token!
When we need to request other service interfaces, we only need to add the Authorization parameter to the request header headers.
When the permission interceptor is verified, the user information can be obtained only through the WebContextUtil utility class in the interface method.
/ / get user token information UserToken userToken = WebContextUtil.getUserToken (); that's all for "how to use JWT to achieve single sign-on". Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.