1. Design ideas
For some interfaces that can only be accessed after login (for example: query my account information), our usual practice is to add a layer of interface verification:
- If the verification is passed, then: return the data normally.
- If the verification fails, then: Throw an exception, telling it that it needs to log in first.
So, what is the basis for judging whether the session is logged in? Let's first briefly analyze the login access process:
- The user submits name + password parameters and calls the login interface.
- If the login is successful, return the Token session credentials of this user.
- Every subsequent request by the user carries this Token.
- The server judges whether the session is successfully logged in based on the Token.
The so-called login authentication refers to the process in which the server verifies the account password and issues a Token session credential to the user. This Token is also the key for us to determine whether the session is logged in or not.
Dynamic graph demo:
Next, we will introduce how to use Sa-Token to complete login authentication in SpringBoot.
Sa-Token is a java authority authentication framework, which mainly solves a series of authority-related issues such as login authentication, authority authentication, single sign-on, OAuth2, and micro-service gateway authentication.
Gitee open source address: https://gitee.com/dromara/sa-token
First introduce the Sa-Token dependency in the project:
<!-- Sa-Token Authentication --> <dependency> <groupId>cn.dev33</groupId> <artifactId>sa-token-spring-boot-starter</artifactId> <version>1.34.0</version> </dependency>
Note: If you are using SpringBoot 3.x, just change sa-token-spring-boot-starter to sa-token-spring-boot3-starter.
2. Login and logout
According to the above ideas, we need a session login function:
// Session login: fill in the parameter with the account id to log in, the recommended data type: long | int | String, complex types cannot be passed in, such as: User, Admin, etc. StpUtil.login(Object id);
Only one line of code can make the session login successful. In fact, Sa-Token has done a lot of work behind the scenes, including but not limited to:
- Check if this account has been logged in before
- Generate Token credentials and Session for the account
- Notify the global listener that the xx account has successfully logged in
- Inject Token into the request context
- Wait for other work...
You don't need to fully understand the entire login process for the time being, you just need to remember the key point: Sa-Token creates a Token credential for this account, and returns it to the front end through the Cookie context.
So in general, our login interface code will be roughly similar to the following:
// session login interface @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { // Step 1: Compare the account name and password submitted by the front end if("zhang".equals(name) && "123456".equals(pwd)) { // Step 2: Log in according to the account id StpUtil.login(10001); return SaResult.ok("login successful"); } return SaResult.error("Login failed"); }
If you have no pressure to read the above code, you may notice something strange: only session login is done here, but Token information is not actively returned to the front end.
Is it because it is not needed? Strictly speaking, it is necessary, but the StpUtil.login(id) method takes advantage of the automatic Cookie injection feature, omitting your handwritten code to return Token.
Don’t worry if you don’t know much about the function of cookies. We will explain the function of cookies in detail in the following [Separation of front and back end] chapters. Now you only need to understand the most basic two points:
- Cookie can write Token value to browser from backend control.
- The Cookie will automatically submit the Token value every time the front end initiates a request.
Therefore, with the support of the Cookie function, we can complete the login authentication with only one line of code StpUtil.login(id).
In addition to the login method, we also need:
// current session logout StpUtil.logout(); // Get whether the current session is logged in, return true=logged in, false=not logged in StpUtil.isLogin(); // Check if the current session is logged in, if not, throw an exception: `NotLoginException` StpUtil.checkLogin();
The exception NotLoginException means that the current session has not been logged in yet, and there are many possible reasons:
The front-end did not submit the Token, the Token submitted by the front-end is invalid, the Token submitted by the front-end has expired...etc.
Sa-Token not registered scene value reference table:
scene value | corresponding constant | Meaning |
---|---|---|
-1 | NotLoginException.NOT_TOKEN | Failed to read Token from request |
-2 | NotLoginException.INVALID_TOKEN | The Token has been read, but the Token is invalid |
-3 | NotLoginException.TOKEN_TIMEOUT | The Token has been read, but the Token has expired |
-4 | NotLoginException.BE_REPLACED | The Token has been read, but the Token has been pushed offline |
-5 | NotLoginException.KICK_OUT | The Token has been read, but the Token has been kicked offline |
So, how to get the scene value? Cut the nonsense and go directly to the code:
// Global exception interception (intercept NotLoginException in the project) @ExceptionHandler(NotLoginException.class) public SaResult handlerNotLoginException(NotLoginException nle) throws Exception { // Print the stack for debugging nle.printStackTrace(); // Judging scene values, customizing exception information String message = ""; if(nle.getType().equals(NotLoginException.NOT_TOKEN)) { message = "not given token"; } else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) { message = "token invalid"; } else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) { message = "token expired"; } else if(nle.getType().equals(NotLoginException.BE_REPLACED)) { message = "token has been pushed offline"; } else if(nle.getType().equals(NotLoginException.KICK_OUT)) { message = "token has been kicked offline"; } else { message = "Current session is not logged in"; } // return to frontend return SaResult.error(message); }
<br/>
Note: The above code is not the best way to process logic. It is just to demonstrate the acquisition and application of scene values with the simplest code. You can customize the processing according to your project requirements.
3. Session query
// Get the current session account id, if not logged in, throw an exception: `NotLoginException` StpUtil.getLoginId(); // Similar query API s include: StpUtil.getLoginIdAsString(); // Get the current session account id, and convert it to `String` type StpUtil.getLoginIdAsInt(); // Get the current session account id and convert it to `int` type StpUtil.getLoginIdAsLong(); // Get the current session account id and convert it to `long` type // ---------- Specify the default value returned when not logged in ---------- // Get the current session account id, if not logged in, return null StpUtil.getLoginIdDefaultNull(); // Get the current session account id, if not logged in, return the default value (`defaultValue` can be any type) StpUtil.getLoginId(T defaultValue);
4. Token query
// Get the token value of the current session StpUtil.getTokenValue(); // Get the token name of the current `StpLogic` StpUtil.getTokenName(); // Get the account id corresponding to the specified token, if not logged in, return null StpUtil.getLoginIdByToken(String tokenValue); // Get the remaining validity period of the current session (unit: s, return -1 means permanent validity) StpUtil.getTokenTimeout(); // Get the token information parameters of the current session StpUtil.getTokenInfo();
TokenInfo is the Token information Model, used to describe the common parameters of a Token:
{ "tokenName": "satoken", // token name "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token value "isLogin": true, // Is this token already logged in? "loginId": "10001", // LoginId corresponding to this token, null when not logged in "loginType": "login", // Account Type Identification "tokenTimeout": 2591977, // token remaining validity period (unit: second) "sessionTimeout": 2591977, // User-Session remaining valid time (unit: second) "tokenSessionTimeout": -2, // Token-Session remaining valid time (unit: second) (-2 means this cache does not exist in the system) "tokenActivityTimeout": -1, // token remaining valid time without operation (unit: second) "loginDevice": "default-device" // Login device type }
5. Take a small test to deepen your understanding
Create a new LoginAuthController and copy the following code
package com.pj.cases.use; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.util.SaResult; /** * Sa-Token Example of login authentication * * @author kong * @since 2022-10-13 */ @RestController @RequestMapping("/acc/") public class LoginAuthController { // Session login interface ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456 @RequestMapping("doLogin") public SaResult doLogin(String name, String pwd) { // Step 1: Check whether the account name & password submitted by the front end are correct, and start logging in after the comparison is successful // This is only a simulation example, real projects need to query data from the database for comparison if("zhang".equals(name) && "123456".equals(pwd)) { // Step 2: Log in according to the account id // The parameters filled in here should keep the user table unique, such as user id, and you cannot directly fill in the entire User object StpUtil.login(10001); // SaResult is a simple encapsulation of the returned result in Sa-Token, the following examples will not go into details return SaResult.ok("login successful"); } return SaResult.error("Login failed"); } // Query current login status ---- http://localhost:8081/acc/isLogin @RequestMapping("isLogin") public SaResult isLogin() { // StpUtil.isLogin() Query whether the current client is logged in, return true or false boolean isLogin = StpUtil.isLogin(); return SaResult.ok("Whether the current client is logged in:" + isLogin); } // Check current login status ---- http://localhost:8081/acc/checkLogin @RequestMapping("checkLogin") public SaResult checkLogin() { // Check if the current session is logged in, if not, throw an exception: `NotLoginException` StpUtil.checkLogin(); // After an exception is thrown, the code will enter the global exception handling (GlobalException.java). If no exception is thrown, it means that the login verification has passed and the following information will be returned return SaResult.ok("Verify that the login is successful, this line of string is the information that will be returned only after login"); } // Get who is currently logged in ---- http://localhost:8081/acc/getLoginId @RequestMapping("getLoginId") public SaResult getLoginId() { // It should be noted that StpUtil.getLoginId() has its own login verification effect // That is to say, if this code is called without logging in, the framework will throw `NotLoginException` exception, the effect is the same as StpUtil.checkLogin() Object userId = StpUtil.getLoginId(); System.out.println("Currently logged in account id yes:" + userId); // If you do not want StpUtil.getLoginId() to trigger the login verification effect, you can fill in a default value // If the session is not logged in, it will return this default value, if the session is logged in, it will normally return the logged in account id Object userId2 = StpUtil.getLoginId(0); System.out.println("Currently logged in account id yes:" + userId2); // Or make it return null when not logged in Object userId3 = StpUtil.getLoginIdDefaultNull(); System.out.println("Currently logged in account id yes:" + userId3); // Type conversion: // StpUtil.getLoginId() returns Object type, you can use the following method to specify the type it returns int userId4 = StpUtil.getLoginIdAsInt(); // Convert the return value to type int long userId5 = StpUtil.getLoginIdAsLong(); // Convert the return value to type long String userId6 = StpUtil.getLoginIdAsString(); // Convert the return value to String type // Question: Isn’t there eight basic types of data? Why only encapsulate the conversion of the above three types? // Because most projects use int, long or String to declare the type of UserId, I have never seen any project use double, float, boolean, etc. to declare UserId System.out.println("Currently logged in account id yes:" + userId4 + " --- " + userId5 + " --- " + userId6); // return to frontend return SaResult.ok("The account currently logged in by the client id yes:" + userId); } // Query Token information ---- http://localhost:8081/acc/tokenInfo @RequestMapping("tokenInfo") public SaResult tokenInfo() { // TokenName is the meaning of the Token name, and this value also determines the parameter name that should be used when the front-end submits the Token String tokenName = StpUtil.getTokenName(); System.out.println("front-end submission Token Parameter names that should be used when:" + tokenName); // Use StpUtil.getTokenValue() to get the Token value submitted by the front end // The default front-end of the framework can submit Token s in the following three ways: // Cookie s (automatically submitted by browser) // Header (code submitted manually) // Query parameter (code submitted manually) For example: /user/getInfo?satoken=xxxx-xxxx-xxxx-xxxx // The reading order is: Query parameter --> Header --> Cookie // If the Token information cannot be read in the above three places, it is considered that the front end has not submitted the Token String tokenValue = StpUtil.getTokenValue(); System.out.println("Submitted by the front end Token Values are:" + tokenValue); // TokenInfo contains most information about this Token SaTokenInfo info = StpUtil.getTokenInfo(); System.out.println("Token name:" + info.getTokenName()); System.out.println("Token value:" + info.getTokenValue()); System.out.println("Are you currently logged in:" + info.getIsLogin()); System.out.println("Currently logged in account id: " + info.getLoginId()); System.out.println("Type of current login account:" + info.getLoginType()); System.out.println("The device type currently logged into the client:" + info.getLoginDevice()); System.out.println("current Token Remaining lifespan for :" + info.getTokenTimeout()); // Unit: second, -1 means permanent, -2 means the value does not exist System.out.println("current Token Remaining temporary validity period for :" + info.getTokenActivityTimeout()); // Unit: second, -1 means permanent, -2 means the value does not exist System.out.println("current User-Session remaining validity period of" + info.getSessionTimeout()); // Unit: second, -1 means permanent, -2 means the value does not exist System.out.println("current Token-Session remaining validity period of" + info.getTokenSessionTimeout()); // Unit: second, -1 means permanent, -2 means the value does not exist // return to frontend return SaResult.data(StpUtil.getTokenInfo()); } // Session logout ---- http://localhost:8081/acc/logout @RequestMapping("logout") public SaResult logout() { // Logging out clears data in three places: // 1. Token information saved in Redis // 2. Token information saved in the current request context // 3. Token information saved in the Cookie (if the Cookie mode is not used, it will not be cleared) StpUtil.logout(); // StpUtil.logout() can also be called successfully when not logged in, // That is to say, no matter whether the client is logged in or not, after executing StpUtil.logout(), it will be in the unlogged state System.out.println("Are you currently logged in:" + StpUtil.isLogin()); // return to frontend return SaResult.ok("Log out successfully"); } }
The code comments have been explained in detail for each step of operation, and you can perform step-by-step tests according to the access links in the comments.
This sample code has been uploaded to Gitee, you can refer to:
Sa-Token login authentication example
References
- Gitee warehouse address: https://gitee.com/dromara/sa-token
- GitHub warehouse address: https://github.com/dromara/sa-token
- Sa-Token online documentation: https://sa-token.dev33.cn/