Process
The front is just the implementation of basic functions, and the latter is the redis shared session application.
Generate verification code
Implementation
public Result sendCode(String phone, HttpSession session) { // Verify mobile phone number //Using a toolkit written by myself for regular verification if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("Mobile phone format error"); } // Generate verification code using the tool in the hutool package to generate random numbers. String code = RandomUtil.randomNumbers(6); //Save verification code to session session.setAttribute("code",code); // Send the verification code return Result.ok(); }
Log in with mobile number and register
Implementation
public Result login(LoginFormDTO loginForm, HttpSession session) { // 1. Verify mobile phone number //Using a toolkit written by myself for regular verification String phone = loginForm.getPhone(); if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("Mobile phone format error"); } // 2. Verify verification code String code = loginForm.getCode(); Object cacheCode = session.getAttribute("code"); if(code == null || !code.equals(cacheCode)){ return Result.fail("The verification code is incorrect"); } // 3. Query users based on mobile phone number User user = lambdaQuery().eq(User::getPhone, phone).one(); // 4. Does the user exist? if(user == null) { // 4.1Create if it does not exist user = createUser(phone,session); } // 5. Save the user to the session if it exists session.setAttribute("user",user); return Result.ok(); } private User createUser(String phone, HttpSession session) { User user = new User(); user.setPhone(phone); user.setNickName(USER_NICK_NAME_PREFIX + phone); save(user); //Put user into session session.setAttribute("user",user); return user; }
Verify login status
Implementation
Interceptor implementation
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. Get the user from the session HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); // 2. Does the user exist? if (user == null) { // If 2.1 does not exist, intercept it response.setStatus(401); Result notLogin = Result.fail("Unauthorized"); response.setContentType("text/json"); response.setCharacterEncoding("utf-8"); response.getWriter().write(new ObjectMapper().writeValueAsString(notLogin)); return false; } // 2.2 exists //Save the user to ThreadLocal, using a tool written by yourself /*UserDTO userDTO = new UserDTO(); userDTO.setId(user.getId()); userDTO.setIcon(user.getIcon()); userDTO.setNickName(user.getNickName());*/ UserHolder.saveUser(BeanUtil.copyProperties(user,UserDTO.class));//Too troublesome, use hutool tool // 3. Release return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // clean data UserHolder.removeUser(); } }
@Configuration public class WebmvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()). excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); } }
Use redis optimization
Login generation verification code optimization
public Result sendCode(String phone, HttpSession session) { // Verify mobile phone number //Using a toolkit written by myself for regular verification if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("Mobile phone format error"); } // Generate verification code using the tool in the hutool package to generate random numbers. String code = RandomUtil.randomNumbers(6); // *modify: Save the verification code to redis stringRedisTemplate.opsForValue() .set(RedisConstants.LOGIN_CODE_KEY + phone,code, RedisConstants.LOGIN_CODE_TTL,TimeUnit.MINUTES); // Set expiration time // Send the verification code log.info("The generated verification code is: {}",code); return Result.ok(); }
Login optimization
public Result login(LoginFormDTO loginForm, HttpSession session) { // 1. Verify mobile phone number //Using a toolkit written by myself for regular verification String phone = loginForm.getPhone(); if (RegexUtils.isPhoneInvalid(phone)) { return Result.fail("Mobile phone format error"); } // 2. Verify verification code String code = loginForm.getCode(); // *modify // *1. Get verification from redis String codeKey = RedisConstants.LOGIN_CODE_KEY + phone; String cacheCode = stringRedisTemplate.opsForValue() .get(codeKey); if(code == null || !code.equals(cacheCode)){ return Result.fail("The verification code is incorrect"); } // *If the verification code is correct, delete the verification code cached in redis stringRedisTemplate.delete(codeKey); // 3. Query users based on mobile phone number User user = lambdaQuery().eq(User::getPhone, phone).one(); // 4. Does the user exist? if(user == null) { // 4.1Create if it does not exist user = createUser(phone,session); } // 5. Save the user to the session if it exists // session.setAttribute("user",user); // *modify: Save users to redis // *1. Convert user to map // *1.1 First convert user to userDto UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); Map<String, Object> map = BeanUtil.beanToMap(userDTO,new HashMap<>(), new CopyOptions() .setIgnoreNullValue(true) //Ignore null value .setFieldValueEditor((field,value) -> value.toString())//Change the value to String type ); // *2. Save map to redis String tokenKey = RedisConstants.LOGIN_USER_KEY + UUID.randomUUID().toString(true); stringRedisTemplate.opsForHash().putAll(tokenKey,map); // *3. Set expiration time stringRedisTemplate.expire(tokenKey,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS); return Result.ok(tokenKey); }
Interceptor optimization
Interceptor configuration
@Configuration public class WebmvcConfig implements WebMvcConfigurer { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)). excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); } }
public class LoginInterceptor implements HandlerInterceptor { private StringRedisTemplate stringRedisTemplate; public LoginInterceptor(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // *modify // *1. Get users from redis // *1.1. First obtain the authorization in the request header String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)) { // 1.2Token does not exist, intercept it response.setStatus(401); Result notLogin = Result.fail("Unauthorized"); response.setContentType("text/json"); response.setCharacterEncoding("utf-8"); response.getWriter().write(new ObjectMapper().writeValueAsString(notLogin)); return false; } // *Get users from redis Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(token); if (entries.isEmpty()) { return false; } // *2Convert map to userDto UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false); // * Save user to ThreadLocal UserHolder.saveUser(userDTO); // *3. Set the expiration time of userDto in redis stringRedisTemplate.expire(token,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // clean data UserHolder.removeUser(); } }
Question
When a user logs in, the accessed resources do not need to be filtered. In this case, the interceptor cannot refresh the token.
The solution is to configure an interceptor to intercept all requests. After refreshing the token, these requests are directly released and handed over to the second filter to determine whether to release them.
Optimized as follows
Interceptor 1
public class RefreshTokenInterceptor implements HandlerInterceptor { private StringRedisTemplate stringRedisTemplate; public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // *modify // *1. Get users from redis // *1.1. First obtain the authorization in the request header String token = request.getHeader("authorization"); if (StrUtil.isBlank(token)) { return true; } // *Get users from redis Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(token); if (entries.isEmpty()) { return true; } // *2Convert map to userDto UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false); // * Save user to ThreadLocal UserHolder.saveUser(userDTO); // *3. Refresh the expiration time of userDto in redis stringRedisTemplate.expire(token,RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // clean data UserHolder.removeUser(); } }
Interceptor 2
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. Get the user from Thread Local UserDTO user = UserHolder.getUser(); // 2. Determine whether the user exists if (user == null) { //2.1 does not exist return false; } //2.2 User exists return true; } }
Configuration
@Configuration public class WebmvcConfig implements WebMvcConfigurer { @Autowired private StringRedisTemplate stringRedisTemplate; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()). excludePathPatterns( "/shop/**", "/voucher/**", "/shop-type/**", "/upload/**", "/blog/hot", "/user/code", "/user/login" ).order(1); registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)) .addPathPatterns("/**").order(0); } }
Summary
When implementing SMS login using redis, you need to pay attention to the following points:
- After generating the verification code, you need to save the generated verification code to redis and set a ttl
- When logging in, you need to obtain the verification code from redis. When the login is successful, you need to delete the verification code in redis.
- After the verification code is entered correctly, after querying the user from the database, the user information needs to be saved in redis and saved using hash. It should be noted that the value is prone to errors during serialization, and ttl must be set.
- During login verification, obtain the user from redis. If it exists, refresh the token time.
- Two interceptors need to be set up. The first one is used to intercept all requests and then refresh the token time. The second one is used to determine whether these requests need to be filtered.