preface

The last article completed the permission management, but login and permission interception has not done, this article briefly describes the design of the login module. The authority of this rapid development framework is not adopted by Spring Security, Shiro and other authority frameworks, not because I think they are not good enough, but because they are too good and too heavy, I am not used to it, so I still plan to make a simple one myself.

About the login

In fact, there is nothing to say about login, but I would like to say it briefly here.

Login into arguments

The common input parameters are the user name, password, and image verification code (not required). Here, the user name and password are used for login.

Troubleshooting method After multiple login failures

When an account exceeds the maximum number of incorrect login attempts, the system automatically locks the account. To log in to the system again, the administrator must unlock the account.

About the Token storage mechanism

JWT is used here for the time being without persistence. Storage implementation with DB and Redis will be considered in the future.

Login Process

  1. If the user exists, 2 is displayed. Otherwise, a service exception is thrown
  2. Check whether the user is locked. If the user is not locked, 3. Otherwise, the service is abnormal
  3. Check whether the number of login errors reaches the threshold. If the number of login errors does not reach the threshold, 4. Otherwise, service exceptions are thrown
  4. Check whether the password is correct. If the password is correct, information about the created login session is displayed. If the password is incorrect, the service is discarded

Login correlation table

The users table (sys_user)

CREATE TABLE 'sys_user' (' id 'bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', 'user_name' varchar(32) NOT NULL COMMENT 'iD ',' real_name 'varchar(32) DEFAULT NULL COMMENT' iD ', 'Avatar' varchar(200) DEFAULT NULL COMMENT 'avatar ',' email 'varchar(64) DEFAULT NULL COMMENT' avatar ', 'mobile_phone' varchar(11) DEFAULT NULL COMMENT 'phone ',' telephone 'varchar(20) DEFAULT NULL COMMENT' phone ', Varchar (10) DEFAULT NULL COMMENT 'add salt ',' salt 'varchar(10) DEFAULT NULL COMMENT' add salt ', ` sex ` int (6) unsigned DEFAULT '1' COMMENT 'gender (2 - > 1 - > MALE | MALE, FEMALE | FEMALE, 3 - > | UNKNOWN UNKNOWN)', Unsigned ` is_locked ` tinyint (1) the DEFAULT '2' COMMENT 'whether lock (1 - > has targeted | YES, 2 - > unlocked | NO)', 'remarking' varchar(255) DEFAULT NULL COMMENT 'remarking ',' create_time 'datetime(3) DEFAULT NULL COMMENT' remarking ', 'update_time' datetime(3) DEFAULT NULL COMMENT 'update_time ', Unsigned ` is_deleted ` tinyint (1) the DEFAULT '1' COMMENT 'whether delete (1 - > not delete | NO, 2 - > deleted | YES)', PRIMARY KEY (` id `), KEY `real_name` (`real_name`), KEY 'user_name' (' user_name ') ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET= UTf8MB4 ROW_FORMAT=COMPACT COMMENT=' user ';Copy the code

Number of user logins (sys_user_login_times)

CREATE TABLE 'sys_user_login_times' (' id' bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', 'user_id' bigint(20) unsigned NOT NULL COMMENT 'user id',' login_IP 'char(15) DEFAULT NULL COMMENT' login IP address ', 'times' int(10) unsigned DEFAULT '1' COMMENT' unsigned DEFAULT ', 'create_time' datetime(3) DEFAULT NULL COMMENT 'create_time ', 'update_time' datetime(3) DEFAULT NULL COMMENT 'update_time ', Unsigned ` is_deleted ` tinyint (1) the DEFAULT '1' COMMENT 'whether delete (1 - > not delete | NO, 2 - > deleted | YES)', PRIMARY KEY (' id ')) ENGINE=InnoDB DEFAULT CHARSET= utf8MB4 COMMENT=' InnoDB ';Copy the code

Spring MVC interceptor

Spring MVC’s interceptor is the handler class that intercepts controller methods. All Controller classes go through the interceptor first, and if the interceptor returns success, the business method of the control class is executed. Custom interceptors typically implement methods on the HandlerInterceptor interface. The following is a brief description of the interface’s methods

public interface HandlerInterceptor {
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// Controller is called before the service processes a request. In this method, it determines the login status of the user and whether the user has the permission to proceed to the next step
        // Return true to continue, return false to terminate.
		return true;
	}
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        // Controller is executed after the business processing request is completed and before the view is generated
	}
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        // is called after the DispatcherServlet has fully processed the request and can be used to clean up the resource}}Copy the code

About the interceptor blacklist

Interceptor blacklist refers to the request that is not blocked or directly allowed after blocking. Generally, the request is processed in the following three ways:

  1. Use excludePathPatterns directly when configuring interceptors to exclude requests that are not intercepted;
  2. In the interceptor to determine whether in the blacklist, in the direct release;
  3. By adding special marks or notes to the control layer method, if the conditions are met, it can be released directly;

The above three methods are generally used in combination. Different processing methods are used according to different scenarios.

Interceptor processing flow

  1. Read the annotation on the control layer method to determine whether there is a release annotation, if there is no 2, otherwise release
  2. Check whether the token exists. If yes, 3. Otherwise, an exception occurs
  3. Check whether the token is valid. If the token is valid, 4 is displayed. Otherwise, the service is abnormal
  4. Check whether the user has the permission. If the user does not have the permission, the service is abnormal

Start coding

The directory structure

├─ ├─ SRC /main/ Java ├─ com.mlDong.modules. Sys ├─ Controller ├─ Dto ├─ ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ Java ├─ SysLoginService. Java └ ─ ─ vo └ ─ ─ SysLoginVo. Java ├ ─ ─ SRC/main/resources └ ─ ─ dao/sys └ ─ ─ sys_user_dao. XML ├ ─ ─ Mldong - common tools and common code ├ ─ ─ SRC/main/Java ├ ─ ─ com.mldong.com mon ├ ─ ─ the annotation └ ─ ─ AuthIgnore. Java ├ ─ ─ the config └ ─ ─ InterceptorConfig. Java ├ ─ ─ interceptor ├ ─ ─ AuthInterceptor. Java └ ─ ─ AuthInterceptorService. Java ├ ─ ─ JWT ├ ─ ─ JwtProperties. Java └ ─ ─ JwtToken. Java └ ─ ─ token ├ ─ ─ impl └ ─ ─ JwtTokenStrategyImpl. Java └ ─ ─ TokenStrategy. Java ├ ─ ─ Mldong-generator Code generatorCopy the code

Core File Description

  • mldong-common/src/main/java/com/mldong/common/token/TokenStrategy.java

Token Storage policy interface

package com.mldong.common.token;

public interface TokenStrategy {
	/** * Generate token * from user ID and user@paramUserId userId *@paramUserName Indicates the password *@return* /
	public String generateToken(Long userId,String userName);
	/** * Check whether the token is valid *@paramToken Temporary token *@return* /
	public boolean verifyToken(String token);
	/** * Get user ID * from token@param token
	 * @return* /
	public Long getUserId(String token);
	/** * Obtain the user name from the token *@param token
	 * @return* /
	public String getUserName(String token);
}
Copy the code
  • mldong-common/src/main/java/com/mldong/common/token/impl/JwtTokenStrategyImpl.java

Token store, JWT implementation class

package com.mldong.common.token.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.mldong.common.jwt.JwtToken;
import com.mldong.common.token.TokenStrategy;

@Component
public class JwtTokenStrategyImpl implements TokenStrategy{
	@Autowired
	private JwtToken jwtToken;

	@Override
	public String generateToken(Long userId, String userName) {
		return jwtToken.generateToken(userId, userName);
	}
	@Override
	public boolean verifyToken(String token) {
		return jwtToken.verify(token);
	}
	@Override
	public Long getUserId(String token) {
		return jwtToken.getUserId(token);
	}
	@Override
	public String getUserName(String token) {
		returnjwtToken.getUserName(token); }}Copy the code
  • mldong-common/src/main/java/com/mldong/common/jwt/JwtToken.java

JWT bean slightly

  • mldong-common/src/main/java/com/mldong/annotation/AuthIgnore.java

Control layer method release annotation

package com.mldong.common.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** * Ignore permission comments *@author mldong
 *
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthIgnore {

}
Copy the code
  • mldong-common/src/main/java/com/mldong/interceptor/AuthInterceptor.java
package com.mldong.common.interceptor;

import io.swagger.annotations.ApiOperation;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import com.mldong.common.access.AccessInitProcessor;
import com.mldong.common.annotation.AuthIgnore;
import com.mldong.common.base.constant.CommonConstants;
import com.mldong.common.base.constant.GlobalErrEnum;
import com.mldong.common.exception.BizException;
@Component
public class AuthInterceptor implements HandlerInterceptor {
	@Autowired(required=false)
	private AuthInterceptorService authInterceptorService;
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		if(handler.getClass().isAssignableFrom(HandlerMethod.class)) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			AuthIgnore authIgnore = handlerMethod.getMethodAnnotation(AuthIgnore.class);
			if(null! = authIgnore) {// Ignore permissions
				return true;
			}
			String token = getToken(request);
			if("".equals(token)) {
				throw new BizException(GlobalErrEnum.GL99990401);
			}
			if(authInterceptorService == null) {
				// if it is empty, do not do the processing
				return true;
			}
			if(! authInterceptorService.verifyToken(token)) {// The token verification fails
				throw new BizException(GlobalErrEnum.GL99990401);
			}
			ApiOperation apiOperation = handlerMethod.getMethodAnnotation(ApiOperation.class);
			String access = AccessInitProcessor.getAccess(apiOperation);
			if(null == access) {
				// No definition
				return true;
			}
			if(! authInterceptorService.hasAuth(token, access)){// No access permission
				throw newBizException(GlobalErrEnum.GL99990403); }}return true;
	}
	private String getToken(HttpServletRequest request) {
		String token = "";
		token = request.getHeader(CommonConstants.TOKEN);
		if(StringUtils.isEmpty(token)) {
			token = request.getParameter(CommonConstants.TOKEN);
		}
		returntoken; }}Copy the code
  • mldong-common/src/main/java/com/mldong/interceptor/AuthInterceptorService.java

The business interface provided to the interceptor, implemented by the business module, is implemented in the SysrBacServiceImp.java class

package com.mldong.common.interceptor;
/** * The service required by the permission interceptor, implemented by the business layer *@author mldong
 *
 */
public interface AuthInterceptorService {
	/** * Verify token *@param token
	 * @return* /
	public boolean verifyToken(String token);
	/** * Whether the user has permission *@param token token
	 * @paramAccess Permission identifier *@return* /
	public boolean hasAuth(String token,String access);
}
Copy the code
  • mldong-admin/src/main/java/com/mldong/modules/sys/SysRbacServiceImpl.java

Interceptor class implementation, code snippet


@Service
public class SysRbacServiceImpl implements SysRbacService.AuthInterceptorService{
	@Autowired
	private AccessInitProcessor accessInitProcessor;
	@Autowired
	private SysUserDao sysUserDao;
	@Autowired
	private SysUserRoleMapper sysUserRoleMapper;
	@Autowired
	private SysRoleAccessMapper sysRoleAccessMapper;
	@Autowired
	private SysRoleMenuMapper sysRoleMenuMapper;
	@Autowired
	private GlobalProperties globalProperties;
	@Autowired
	private TokenStrategy tokenStrategy;
    @Override
	public boolean hasAccess(Long userId, String access) {
		if(userId.equals(globalProperties.getSuperAdminId())) {
			return true;
		}
 		return loadUserAccessList(userId).contains(access);
	}
	@Override
	public boolean verifyToken(String token) {
		return tokenStrategy.verifyToken(token);
	}
	@Override
	public boolean hasAuth(String token, String access) {
		Long userId = tokenStrategy.getUserId(token);
		returnhasAccess(userId, access); }}Copy the code
  • mldong-common/src/main/java/com/mldong/config/InterceptorConfig.java

Custom interceptor configuration that excludes Swaggerui

package com.mldong.common.config;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer{
	@Autowired(required=false)
	private List<HandlerInterceptor> interceptorList;
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		if(interceptorList! =null) {
			interceptorList.forEach(interceptor->{
				registry.addInterceptor(interceptor)
				.excludePathPatterns("/swagger-resources/**"); }); }}}Copy the code
  • mldong-admin/src/main/java/com/mldong/modules/sys/SysLoginService.java

Login Interface Definition

package com.mldong.modules.sys.service;

import com.mldong.modules.sys.dto.SysLoginParam;
import com.mldong.modules.sys.vo.SysLoginVo;

/** * Login interface *@author mldong
 *
 */
public interface SysLoginService {
	/** * login *@param param
	 * @return* /
	public SysLoginVo login(SysLoginParam param);
	/** * exit *@paramToken token *@return* /
	public int logout(String token);
}
Copy the code
  • mldong-admin/src/main/java/com/mldong/modules/sys/impl/SysLoginServiceImpl.java

Login implementation class

package com.mldong.modules.sys.service.impl;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mldong.common.base.YesNoEnum;
import com.mldong.common.config.GlobalProperties;
import com.mldong.common.exception.BizException;
import com.mldong.common.token.TokenStrategy;
import com.mldong.common.tool.Md5Tool;
import com.mldong.modules.sys.dto.SysLoginParam;
import com.mldong.modules.sys.entity.SysUser;
import com.mldong.modules.sys.entity.SysUserLoginTimes;
import com.mldong.modules.sys.enums.SysErrEnum;
import com.mldong.modules.sys.mapper.SysUserLoginTimesMapper;
import com.mldong.modules.sys.mapper.SysUserMapper;
import com.mldong.modules.sys.service.SysLoginService;
import com.mldong.modules.sys.service.SysRbacService;
import com.mldong.modules.sys.vo.SysLoginVo;
/** * Login interface implementation *@author mldong
 *
 */
@Service
public class SysLoginServiceImpl implements SysLoginService{
	@Autowired
	private SysUserMapper sysUserMapper;
	@Autowired
	private SysUserLoginTimesMapper sysUserLoginTimesMapper;
	@Autowired
	private GlobalProperties globalProperties;
	@Autowired
	private TokenStrategy generateTokenStrategy;
	@Autowired
	private SysRbacService sysRbacService;
	@Transactional(rollbackFor=Exception.class)
	@Override
	public SysLoginVo login(SysLoginParam param) {
		String userName = param.getUserName();
		String password = param.getPassword();
		SysUser q = new SysUser();
		q.setUserName(userName);
		SysUser user = sysUserMapper.selectOne(q);
		if(null == user) {
			// The user does not exist
			throw new BizException(SysErrEnum.SYS80000001);
		}
		if(YesNoEnum.YES.equals(user.getIsLocked())) {
			// The user has been locked
			throw new BizException(SysErrEnum.SYS80000004);
		}
		// Verify login times
		SysUserLoginTimes userLoginTimesQuery = new SysUserLoginTimes();
		userLoginTimesQuery.setUserId(user.getId());
		SysUserLoginTimes userLoginTimes = sysUserLoginTimesMapper.selectOne(userLoginTimesQuery);
		if(null == userLoginTimes || 
			(null! = userLoginTimes && userLoginTimes.getTimes()<=globalProperties.getMaxErrLoginTimes())) { String passwordEncry = Md5Tool.md5(password, user.getSalt());if(! passwordEncry.equals(user.getPassword())) {// The user name or password is incorrect
				Date now = new Date();
				if(userLoginTimes == null) {
					userLoginTimes = new SysUserLoginTimes();
					userLoginTimes.setUserId(user.getId());
					userLoginTimes.setCreateTime(now);
					userLoginTimes.setUpdateTime(now);
					userLoginTimes.setTimes(1);
					sysUserLoginTimesMapper.insertSelective(userLoginTimes);
				} else {
					userLoginTimes.setUserId(null);
					userLoginTimes.setCreateTime(null);
					userLoginTimes.setUpdateTime(now);
					userLoginTimes.setTimes(userLoginTimes.getTimes()+1);
					sysUserLoginTimesMapper.updateByPrimaryKeySelective(userLoginTimes);
				}
				throw newBizException(SysErrEnum.SYS80000002); }}else {
			// Too many logins, the account has been locked, please contact the administrator
			throw new BizException(SysErrEnum.SYS80000003);
		}
		return createLoginVo(user);
	}
	@Transactional(rollbackFor=Exception.class)
	@Override
	public int logout(String token) {
		return 1;
	}
	/** * Create login return vo *@param user
	 * @return* /
	private SysLoginVo createLoginVo (SysUser user) {
		SysLoginVo vo = new SysLoginVo();
		Long userId = user.getId();
		String avatar = user.getAvatar();
		String userName = user.getUserName();
		String realName = user.getRealName();
		/ / create a token
		String token = generateTokenStrategy.generateToken(userId, userName);
		vo.setAccessList(sysRbacService.loadUserAccessList(userId));
		vo.setAvatar(avatar);
		vo.setMenuList(sysRbacService.loadUserMenuList(userId));
		vo.setRealName(realName);
		vo.setUserId(userId);
		vo.setUserName(userName);
		vo.setToken(token);
		returnvo; }}Copy the code
  • mldong-admin/src/main/java/com/mldong/modules/sys/controller/SysLoginController.java

Login control layer class

package com.mldong.modules.sys.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.mldong.common.annotation.AuthIgnore;
import com.mldong.common.base.CommonResult;
import com.mldong.common.base.constant.CommonConstants;
import com.mldong.modules.sys.dto.SysLoginParam;
import com.mldong.modules.sys.service.SysLoginService;
import com.mldong.modules.sys.vo.SysLoginVo;

@RestController
@Api(tags="Sys - Login module")
public class SysLoginController {
	@Autowired
	private SysLoginService sysLoginService;
	/** * Log in to system *@param param
	 * @return* /
	@PostMapping("/sys/login")
	@AuthIgnore
	@ApiOperation(value="Login system", notes="Login system")
	public CommonResult<SysLoginVo> login(@RequestBody @Validated SysLoginParam param) {
		return CommonResult.success("Login successful", sysLoginService.login(param));
	}
	/** * exit the system *@return* /
	@PostMapping("/sys/logout")
	@ApiOperation(value=Log out of the system, notes=Log out of the system)
	@AuthIgnore
	publicCommonResult<? > logout(HttpServletRequest request) { String token = getToken(request); sysLoginService.logout(token);return CommonResult.success("Exit successful".null);
	}
	private String getToken(HttpServletRequest request) {
		String token = "";
		token = request.getHeader(CommonConstants.TOKEN);
		if(StringUtils.isEmpty(token)) {
			token = request.getParameter(CommonConstants.TOKEN);
		}
		returntoken; }}Copy the code
  • mldong-admin/src/main/java/com/mldong/modules/sys/vo/SysLoginVo.java

Vo is returned after successful login

package com.mldong.modules.sys.vo;

import io.swagger.annotations.ApiModelProperty;

import java.io.Serializable;
import java.util.List;

import com.mldong.modules.sys.entity.SysMenu;

public class SysLoginVo implements Serializable{

	/ * * * * /
	private static final long serialVersionUID = 1387112822760941352L;
	
	@ApiModelProperty(value="Temporary Token")
	private String token;
	@ApiModelProperty(value="User id")
	private Long userId;
	@ApiModelProperty(value="Username")
	private String userName;
	@ApiModelProperty(value="Name")
	private String realName;
	@ApiModelProperty(value="Avatar")
	private String avatar;
	@ApiModelProperty(value="Permission Identification Set")
	private List<String> accessList;
	@ApiModelProperty(value="Routing Menu Set")
	private List<SysMenu> menuList;

	/ / get the set slightly
}
Copy the code

summary

Login seems to be a simple function, but to really do well, is not very easy. Because not only to consider the implementation, to consider the subsequent extension, such as token storage mechanism, interceptor business method extraction, interface oriented programming……

Project source code address

  • The back-end

Gitee.com/mldong/mldo…

  • The front end

Gitee.com/mldong/mldo…

Related articles

Create a suitable for their own rapid development framework – the pilot

Build a suitable for their own rapid development framework – back-end scaffolding

Build a fast development framework for yourself – integrated Mapper

Build a fast development framework for yourself – integration with Swaggerui and KNIfe4J

Build a suitable for their own rapid development framework – universal class packaging unified result return, unified exception handling

Create a quick development framework for yourself – business error code specifications and practices

Build a quick development framework for yourself – framework layering and CURD sample

Create a suitable for their own rapid development framework – Mapper logical deletion and enumeration type specification

Create a suitable framework for rapid development – Hibernate Validator data verification

Create a suitable for their own rapid development framework – code generator principle and implementation

Create a suitable for their own rapid development framework – universal query design and implementation

Build a suitable rapid development framework – rBAC-based permission management