1. Common Authentication Mechanisms
1.Http Basic Auth
Http Basic Auth provides the user's username and password every time the API is requested. In short, Basic Auth is the simplest authentication method used with Restful API, and only needs to provide the username and password. However, due to the risk of exposing usernames and passwords to third-party clients, they are less and less used in production environments. Therefore, when developing Restful API s that are open to the outside world, Http Basic Auth authentication should be avoided as much as possible.
2.Cookie Auth
The cookie authentication mechanism is to create a Session object on the server side for a request authentication, and at the same time create a Cookie object on the client's browser side, and implement state management by matching the Cookie object carried by the client with the Session object on the server side. By default, the Cookie will be deleted when the browser is closed, and the Cookie can be valid for a certain period of time by setting the timeout period of the Cookie.
3.OAuth third-party authorization
OAuth (Open Authorization) is an open authorization standard that provides a secure, open and simple standard for user resource authorization. The difference from the previous authorization method is that the authorization of OAuth will not allow the third party to access the user's account information (such as user name and password), that is, the third party can apply for the user's resource without using the user's user name and password. authorization, so OAuth is secure.
OAuth sets up an authorization layer between the "client" and the "service provider". The "client" cannot log into the "service provider" directly, only the authorization layer, which distinguishes the user from the client. The token used by the "client" to log in to the authorization layer, which is different from the user's password. The user can specify the permission scope and validity period of the authorization layer token when logging in. After the "client" logs in to the authorization layer, the "service provider" opens the user's stored data to the "client" according to the permission scope and validity period of the token.
This OAuth-based authentication mechanism is used for personal consumption Internet products, such as social networking, business super apps and other applications, but it is not suitable for enterprise applications with their own authentication authority management
4. Token Auth
With the Token-based authentication method, there is no need to store the user's login information on the server side. The process is as follows:
-
The client requests a login using a username and password.
-
The server receives the request to verify the username and password.
-
After the verification is successful, the server will issue a Token, and then send the Token to the client.
-
After the client receives the Token, it can store it locally in the Cookie.
-
The client needs to carry the Token in the Cookie every time it requests resources from the server.
-
After the server receives the request, it verifies the Token carried by the client, and returns the data if the verification is successful.
Token authentication is more secure than Http Basic Auth, saves server resources than Cookie Auth, and is lighter than OAuth. Token Auth has the following advantages:
-
Support cross-domain access: Cookie s do not allow cross-domain access, which does not exist for the Token mechanism, provided that the transmitted user authentication information is transmitted through the Http header
-
Stateless (extensible on the server side): The Token mechanism does not need to store session information on the server side, because the Token itself contains part of the information of the logged-in user, and only needs to store state information in the client's cookie or local media.
-
It is more suitable for CDN: all the data (such as js, html, pictures, etc.) on the server side can be requested through the content distribution network, and the server side only needs to provide API.
-
Decoupling: No need to be tied to a specific authentication scheme. Tokens can be generated anywhere, as long as the Token is generated when the API is called.
-
More suitable for mobile applications: when the mobile device does not support Cookie authentication, Token authentication can be used.
-
CSRF: Because it no longer relies on cookies, there is no need to consider CSRF (cross-site request forgery) prevention.
-
Performance: A network round-trip time (querying Session information through the database) is more laborious than doing a SHA256 calculation of Token verification and parsing.
-
Standardization-based: API s can be created in standard JSON Web Token (JWT). This standard already exists with multiple backend libraries (.net, Ruby, Java, Python, Php) and support from multiple companies (Firebase, Google, Microsoft).
2. Introduction to JWT
1. What is JWT
JSON Web Token (JWT) is an open industry standard (RFC 7519), which defines a concise, self-contained protocol format for passing json objects between communication parties. The information passed can be verified and verified by digital signatures. trust. JWT can be signed using HMAC algorithm or using RSA public/private key pair to prevent tampering.
JWT official website: https://jwt.io
Advantages of JWT tokens:
-
JWT is based on json, which is very convenient to parse.
-
Rich content can be customized in the token, easy to expand.
-
Through asymmetric encryption algorithm and digital signature technology, JWT is tamper-proof and has high security.
-
The resource service uses JWT to complete authorization without relying on the authentication service.
Disadvantages of JWT tokens:
-
The JWT token is long and occupies a large storage space.
2.JWT composition
A JWT is actually a string, which consists of three parts, header, payload and signature.
1) Header
The header is used to describe the most basic information about the JWT, such as its type (i.e. JWT) and the algorithm used for signing (such as HMAC SHA256 or RSA). This can also be represented as a JSON object.
{ "alg":"HS256", "typ":"JWT" }
-
alg: signature algorithm
-
typ: type
We BASE64 encode the json string in the header, and the encoded string is as follows:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Base64 is a representation of binary data based on 64 printable strings. JDK provides very convenient Base64Encoder and Base64Decoder, which can be used to easily complete Base64-based encoding and decoding.
2) Payload
The payload is a place to store valid information, such as the user's basic information can be stored in this part. The payload consists of three parts:
-
Lives registered in the standard (recommended but not mandatory)
-
iss::jwt issuer
-
sub: users for which jwt is intended
-
aud: the party receiving the jwt
-
exp: the expiration time of jwt, the expiration time must be greater than the issuance time
-
nbf: Define until when the jwt is unavailable
-
iat: issue time of jwt
-
jti: The unique identifier of jwt, which is mainly used as a one-time token to avoid replay attacks.
-
-
public statement
The public statement can add any information, generally adding user-related information or other necessary information for business needs, but it is not recommended to add sensitive information, because this part can be decrypted on the client side.
-
private statement
The private statement is a statement jointly defined by the provider and the consumer. Generally, it is not recommended to store sensitive information, because base64 is symmetric decryption, which means that this part of the information can be classified as plaintext information.
Private claims are also custom claims, which are used to store custom key-value pairs.
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
Where sub is a standard statement, name is a custom private statement, encoded as follows:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
3) Visa, Signature (Signature)
The third part of the jwt is a visa information, which consists of three parts:
-
Header (after Base64 encoding)
-
Payload (after Base64 encoding)
-
Secret (salt, must be kept secret)
This part requires the use of Base64-encrypted header and base4-encrypted payload. The string formed by concatenation is then encrypted by the encryption method re-declared by the header to add salt and Secret, and then form the third part of the JWT - using " qfjava" as the salt:
eZqdTo1mRMB-o7co1oAiTvNvumfCkt-1H-CdfNm78Cw
As you can see from the official tool, the complete string composed of three parts:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.eZqdTo1mRMB-o7co1oAiTvNvumfCkt-1H-CdfNm78Cw
Note: secret is stored on the server side, and jwt is issued and generated on the server side. Secret is used to issue and verify jwt, so it is the private key on the server side and should not be leaked in any scenario. Once the client knows this secret, it means that the client can self-issue jwt.
Third, the use of JJWT
1. What is JJWT
JJWT is an open source Java library that provides end-to-end JWT creation and validation. That is to say, using JJWT can quickly complete the function development of JWT.
2. Quick Start
1) Introduce dependencies
Create a Springboot project and introduce jjwt dependencies, pom.xml is as follows:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.qf</groupId> <artifactId>boot-jjwt-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>boot-jjwt-demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--jjwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2) Create Token
@Test public void testCrtToken(){ //Create JWT object JwtBuilder builder = Jwts.builder().setId("1001")//Set payload content .setSubject("Xiao Ming") .setIssuedAt(new Date())//Set issue time .signWith(SignatureAlgorithm.HS256, "qfjava");//Set the signing key //build token String token = builder.compact(); System.out.println(token); }
JWT converts user information into Token string, and the result is as follows:
//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDAxIiwic3ViIjoi5bCP5piOIiwiaWF0IjoxNjE1MzY2MDEyfQ.2LNcw1v64TNQ96eCpWKvtAccBUA-cEVMDyJNMef-zu0
3) Parse Token
The Token is parsed through JWT, and the user information stored in the Token is obtained, that is, the Claims object is generated.
@Test public void testParseToken(){ String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDAxIiwic3ViIjoi5bCP5piOIiwiaWF0IjoxNjE1MzY2MDEyfQ.2LNcw1v64TNQ96eCpWKvtAccBUA-cEVMDyJNMef-zu0"; //Parse the Token, generate the Claims object, and parse the user information stored in the Token into the claims object Claims claims = Jwts.parser().setSigningKey("qfjava").parseClaimsJws(token).getBody(); System.out.println("id:" + claims.getId()); System.out.println("subject:" + claims.getSubject()); System.out.println("IssuedAt:" + claims.getIssuedAt()); }
The analysis results are as follows:
id:1001 subject:Xiao Ming IssuedAt:Wed Mar 10 16:46:52 CST 2021
4) Token expiration check
During the validity period, the Token can be read normally. After the validity period, the Token will be invalid.
@Test public void testExpToken(){ long now = System.currentTimeMillis(); //current time long exp = now + 1000 * 60; //Expiration time is 1 minute JwtBuilder builder = Jwts.builder().setId("1001") .setSubject("Xiao Ming") .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "qfjava") .setExpiration(new Date(exp));//set timeout }
5) Custom claims
In addition to setting property values using the official api, you can also add custom key-value pairs.
@Test public void testCustomClaims(){ long now = System.currentTimeMillis(); //current time long exp = now + 1000 * 60; //Expiration time is 1 minute JwtBuilder builder = Jwts.builder().setId("1001") .setSubject("Xiao Ming") .setIssuedAt(new Date()) .signWith(SignatureAlgorithm.HS256, "qfjava") .setExpiration(new Date(exp)) .claim("role", "admin");//Set custom key-value pairs }
Use the following statement to get the property value:
claims.get("role")
Fourth, Spring Security + JWT realizes the authorization verification of the background management system
1. Introduction to the permission module of the background management system
The authorization verification of the background management system is a very common technical scenario in the enterprise. In short, different users have different permissions, and the system capabilities corresponding to the corresponding permissions are also different. For example, the administrator user has all the permissions of the system:
In the above figure, users with administrator roles can see the permissions on the left menu bar, while users with ordinary user roles have different permissions:
At the same time, these permissions can be set through the background management system:
2. Database table design
The core tables of the permission module are as follows: user table, role table, permission table, user role table, role permission table
-
User table: store user data
-
role table: stores role data
-
Permission Table: Stores permission data
-
User role table: the association table of users and roles, which user belongs to which role
-
Role permission table: the association table of roles and permissions, which role has what permissions
For example, the user "Xiao Ming" belongs to the admin role, and the admin role has the authority of the super administrator, so Xiao Ming has the authority of the super administrator.
The database DDL is as follows:
SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_permission`; CREATE TABLE `sys_permission` ( `id` int NOT NULL AUTO_INCREMENT, `parentId` int NOT NULL, `name` varchar(50) NOT NULL, `css` varchar(30) DEFAULT NULL, `href` varchar(1000) DEFAULT NULL, `type` tinyint(1) NOT NULL, `permission` varchar(50) DEFAULT NULL, `sort` int NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Records of sys_permission -- ---------------------------- BEGIN; INSERT INTO `sys_permission` VALUES (1, 0, 'User Management', 'fa-users', '', 1, '', 1); INSERT INTO `sys_permission` VALUES (2, 1, 'User query', 'fa-user', 'pages/user/userList.html', 1, '', 2); INSERT INTO `sys_permission` VALUES (3, 2, 'Inquire', '', '', 2, 'sys:user:query', 100); INSERT INTO `sys_permission` VALUES (4, 2, 'new', '', '', 2, 'sys:user:add', 100); INSERT INTO `sys_permission` VALUES (6, 0, 'change Password', 'fa-pencil-square-o', 'pages/user/changePassword.html', 1, 'sys:user:password', 4); INSERT INTO `sys_permission` VALUES (7, 0, 'system settings', 'fa-gears', '', 1, '', 5); INSERT INTO `sys_permission` VALUES (8, 7, 'menu', 'fa-cog', 'pages/menu/menuList.html', 1, '', 6); INSERT INTO `sys_permission` VALUES (9, 8, 'Inquire', '', '', 2, 'sys:menu:query', 100); INSERT INTO `sys_permission` VALUES (10, 8, 'new', '', '', 2, 'sys:menu:add', 100); INSERT INTO `sys_permission` VALUES (11, 8, 'delete', '', '', 2, 'sys:menu:del', 100); INSERT INTO `sys_permission` VALUES (12, 7, 'Role', 'fa-user-secret', 'pages/role/roleList.html', 1, '', 7); INSERT INTO `sys_permission` VALUES (13, 12, 'Inquire', '', '', 2, 'sys:role:query', 100); INSERT INTO `sys_permission` VALUES (14, 12, 'new', '', '', 2, 'sys:role:add', 100); INSERT INTO `sys_permission` VALUES (15, 12, 'delete', '', '', 2, 'sys:role:del', 100); INSERT INTO `sys_permission` VALUES (16, 0, 'file management', 'fa-folder-open', 'pages/file/fileList.html', 1, '', 8); INSERT INTO `sys_permission` VALUES (17, 16, 'Inquire', '', '', 2, 'sys:file:query', 100); INSERT INTO `sys_permission` VALUES (18, 16, 'delete', '', '', 2, 'sys:file:del', 100); INSERT INTO `sys_permission` VALUES (22, 0, 'Announcement management', 'fa-book', 'pages/notice/noticeList.html', 1, '', 12); INSERT INTO `sys_permission` VALUES (23, 22, 'Inquire', '', '', 2, 'notice:query', 100); INSERT INTO `sys_permission` VALUES (24, 22, 'Add to', '', '', 2, 'notice:add', 100); INSERT INTO `sys_permission` VALUES (25, 22, 'delete', '', '', 2, 'notice:del', 100); INSERT INTO `sys_permission` VALUES (26, 0, 'log query', 'fa-reorder', 'pages/log/logList.html', 1, 'sys:log:query', 13); COMMIT; -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `description` varchar(100) DEFAULT NULL, `createTime` datetime NOT NULL, `updateTime` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Records of sys_role -- ---------------------------- BEGIN; INSERT INTO `sys_role` VALUES (1, 'ADMIN', 'administrator', '2021-02-01 13:25:39', '2021-03-10 09:25:05'); INSERT INTO `sys_role` VALUES (2, 'USER', 'general user', '2021-02-01 21:47:31', '2021-03-10 09:25:26'); COMMIT; -- ---------------------------- -- Table structure for sys_role_permission -- ---------------------------- DROP TABLE IF EXISTS `sys_role_permission`; CREATE TABLE `sys_role_permission` ( `roleId` int NOT NULL, `permissionId` int NOT NULL, PRIMARY KEY (`roleId`,`permissionId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Records of sys_role_permission -- ---------------------------- BEGIN; INSERT INTO `sys_role_permission` VALUES (1, 1); INSERT INTO `sys_role_permission` VALUES (1, 2); INSERT INTO `sys_role_permission` VALUES (1, 3); INSERT INTO `sys_role_permission` VALUES (1, 4); INSERT INTO `sys_role_permission` VALUES (1, 6); INSERT INTO `sys_role_permission` VALUES (1, 7); INSERT INTO `sys_role_permission` VALUES (1, 8); INSERT INTO `sys_role_permission` VALUES (1, 9); INSERT INTO `sys_role_permission` VALUES (1, 10); INSERT INTO `sys_role_permission` VALUES (1, 11); INSERT INTO `sys_role_permission` VALUES (1, 12); INSERT INTO `sys_role_permission` VALUES (1, 13); INSERT INTO `sys_role_permission` VALUES (1, 14); INSERT INTO `sys_role_permission` VALUES (1, 15); INSERT INTO `sys_role_permission` VALUES (1, 16); INSERT INTO `sys_role_permission` VALUES (1, 17); INSERT INTO `sys_role_permission` VALUES (1, 18); INSERT INTO `sys_role_permission` VALUES (1, 22); INSERT INTO `sys_role_permission` VALUES (1, 23); INSERT INTO `sys_role_permission` VALUES (1, 24); INSERT INTO `sys_role_permission` VALUES (1, 25); INSERT INTO `sys_role_permission` VALUES (1, 26); INSERT INTO `sys_role_permission` VALUES (2, 1); INSERT INTO `sys_role_permission` VALUES (2, 2); INSERT INTO `sys_role_permission` VALUES (2, 3); INSERT INTO `sys_role_permission` VALUES (2, 4); INSERT INTO `sys_role_permission` VALUES (2, 6); INSERT INTO `sys_role_permission` VALUES (2, 7); INSERT INTO `sys_role_permission` VALUES (2, 8); INSERT INTO `sys_role_permission` VALUES (2, 9); INSERT INTO `sys_role_permission` VALUES (2, 10); INSERT INTO `sys_role_permission` VALUES (2, 11); INSERT INTO `sys_role_permission` VALUES (2, 12); INSERT INTO `sys_role_permission` VALUES (2, 13); INSERT INTO `sys_role_permission` VALUES (2, 14); INSERT INTO `sys_role_permission` VALUES (2, 15); INSERT INTO `sys_role_permission` VALUES (2, 16); INSERT INTO `sys_role_permission` VALUES (2, 17); INSERT INTO `sys_role_permission` VALUES (2, 18); COMMIT; -- ---------------------------- -- Table structure for sys_role_user -- ---------------------------- DROP TABLE IF EXISTS `sys_role_user`; CREATE TABLE `sys_role_user` ( `userId` int NOT NULL, `roleId` int NOT NULL, PRIMARY KEY (`userId`,`roleId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Records of sys_role_user -- ---------------------------- BEGIN; INSERT INTO `sys_role_user` VALUES (1, 1); INSERT INTO `sys_role_user` VALUES (2, 2); COMMIT; -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(60) NOT NULL, `nickname` varchar(255) DEFAULT NULL, `headImgUrl` varchar(255) DEFAULT NULL, `phone` varchar(11) DEFAULT NULL, `telephone` varchar(30) DEFAULT NULL, `email` varchar(50) DEFAULT NULL, `birthday` date DEFAULT NULL, `sex` tinyint(1) DEFAULT NULL, `status` tinyint(1) NOT NULL DEFAULT '1', `createTime` datetime NOT NULL, `updateTime` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ---------------------------- -- Records of sys_user -- ---------------------------- BEGIN; INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$iYM/H7TrSaLs7XyIWQdGwe1xf4cdmt3nwMja6RT0wxG5YY1RjN0EK', 'administrator', NULL, '', '', '', '1998-07-01', 0, 1, '2021-02-10 15:21:38', '2021-03-06 09:20:19'); INSERT INTO `sys_user` VALUES (2, 'user', '$2a$10$ooGb4wjT7Hg3zgU2RhZp6eVu3jvG29i/U4L6VRwiZZ4.DZ0OOEAHu', 'user', NULL, '', '', '', NULL, 1, 1, '2021-02-01 21:47:18', '2021-03-01 21:47:18'); COMMIT;
3. Login logic
The user checks the username and password when logging in. If the verification is successful, go to the next step (create a Token with JWT and return the Token to the user).
The key code is as follows:
/** * spring security Login processing */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Autowired private PermissionDao permissionDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser = userService.getUser(username); if (sysUser == null) { throw new AuthenticationCredentialsNotFoundException("Username does not exist"); } else if (sysUser.getStatus() == Status.LOCKED) { throw new LockedException("User is locked,Please contact the administrator"); } else if (sysUser.getStatus() == Status.DISABLED) { throw new DisabledException("User has expired"); } LoginUser loginUser = new LoginUser(); BeanUtils.copyProperties(sysUser, loginUser); List<Permission> permissions = permissionDao.listByUserId(sysUser.getId()); loginUser.setPermissions(permissions); return loginUser; } }
The UserDetailsService interface performs login verification, and verifies the username and password from the database through loadUserByUsername(). The verification logic is shown in the above code. The UserDetails object is returned if the verification is successful, and an exception is thrown if the verification fails.
4. After successful login, Token is generated and stored
After successful login, store the user information in the request message into redis, and use JWT to generate a Token and return it to the user
1) Generate Token
/** * Successful login, return Token * * @return */ @Bean public AuthenticationSuccessHandler loginSuccessHandler() { return new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { LoginUser loginUser = (LoginUser) authentication.getPrincipal(); Token token = tokenService.saveToken(loginUser);//Use jwt to generate Token ResponseUtil.responseJson(response, HttpStatus.OK.value(), token); } }; }
2) Use JWT to generate Token
/** * token The implementation class stored in redis<br> * jwt Implemented token * */ @Primary @Service public class TokenServiceJWTImpl implements TokenService { private static final Logger log = LoggerFactory.getLogger("adminLogger"); /** * token Expiration seconds */ @Value("${token.expire.seconds}") private Integer expireSeconds; @Autowired private RedisTemplate<String, LoginUser> redisTemplate; @Autowired private SysLogService logService; /** * private key */ @Value("${token.jwtSecret}") private String jwtSecret; private static Key KEY = null; private static final String LOGIN_USER_KEY = "LOGIN_USER_KEY"; @Override public Token saveToken(LoginUser loginUser) { loginUser.setToken(UUID.randomUUID().toString()); cacheLoginUser(loginUser);//Store user information in redis // login log logService.save(loginUser.getId(), "login", true, null); //Generate token using jwt String jwtToken = createJWTToken(loginUser); return new Token(jwtToken, loginUser.getLoginTime()); } /** * generate jwt * * @param loginUser * @return */ private String createJWTToken(LoginUser loginUser) { Map<String, Object> claims = new HashMap<>(); claims.put(LOGIN_USER_KEY, loginUser.getToken());// Put a random string through which the logged in user can be found String jwtToken = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()) .compact(); return jwtToken; } private void cacheLoginUser(LoginUser loginUser) { loginUser.setLoginTime(System.currentTimeMillis()); loginUser.setExpireTime(loginUser.getLoginTime() + expireSeconds * 1000); // Cache loginUser based on uuid redisTemplate.boundValueOps(getTokenKey(loginUser.getToken())).set(loginUser, expireSeconds, TimeUnit.SECONDS); } /** * Update cached user information */ @Override public void refresh(LoginUser loginUser) { cacheLoginUser(loginUser); } @Override public LoginUser getLoginUser(String jwtToken) { String uuid = getUUIDFromJWT(jwtToken); if (uuid != null) { return redisTemplate.boundValueOps(getTokenKey(uuid)).get(); } return null; } }
After the login is successful, the user data is obtained from the request header, and the user data is stored in the redis with the corresponding key-value pair, and the token is generated using jwt and returned to the client.
public class ResponseUtil { public static void responseJson(HttpServletResponse response, int status, Object data) { try { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "*"); response.setContentType("application/json;charset=UTF-8"); response.setStatus(status); //token data return response.getWriter().write(JSONObject.toJSONString(data)); } catch (IOException e) { e.printStackTrace(); } } }
5. Get user data from Token through JWT
After the user completes the login, when accessing other resources of the server, it will go through the TokenFilter filter, which will obtain the Token in the user request data, and parse it with JWT to obtain the user data.
The key code is as follows:
/** * Token filter */ @Component public class TokenFilter extends OncePerRequestFilter { public static final String TOKEN_KEY = "token"; @Autowired private TokenService tokenService; @Autowired private UserDetailsService userDetailsService; private static final Long MINUTES_10 = 10 * 60 * 1000L; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getToken(request); if (StringUtils.isNotBlank(token)) { //Obtain user data by parsing the Token in the request data through JWT LoginUser loginUser = tokenService.getLoginUser(token); if (loginUser != null) { loginUser = checkLoginTime(loginUser); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } /** * Check Time<br> * The expiration time is compared with the current time. If it is within 10 minutes of the expiration, the cache will be automatically refreshed. * * @param loginUser * @return */ private LoginUser checkLoginTime(LoginUser loginUser) { long expireTime = loginUser.getExpireTime(); long currentTime = System.currentTimeMillis(); if (expireTime - currentTime <= MINUTES_10) { String token = loginUser.getToken(); loginUser = (LoginUser) userDetailsService.loadUserByUsername(loginUser.getUsername()); loginUser.setToken(token); tokenService.refresh(loginUser); } return loginUser; } /** * Get token based on parameters or header * * @param request * @return */ public static String getToken(HttpServletRequest request) { String token = request.getParameter(TOKEN_KEY); if (StringUtils.isBlank(token)) { token = request.getHeader(TOKEN_KEY); } return token; } }
JWT parses Token and returns user data
@Override public LoginUser getLoginUser(String jwtToken) { String uuid = getUUIDFromJWT(jwtToken); if (uuid != null) { return redisTemplate.boundValueOps(getTokenKey(uuid)).get(); } return null; } private String getUUIDFromJWT(String jwtToken) { if ("null".equals(jwtToken) || StringUtils.isBlank(jwtToken)) { return null; } try { Map<String, Object> jwtClaims = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwtToken).getBody(); return MapUtils.getString(jwtClaims, LOGIN_USER_KEY); } catch (ExpiredJwtException e) { log.error("{}expired", jwtToken); } catch (Exception e) { log.error("{}", e); } return null; }
The project is implemented by LayUI+Springboot+Spring Security+JWT+Redis+Mysql. Please refer to README.md for the operation method of the project.