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
- If the user exists, 2 is displayed. Otherwise, a service exception is thrown
- Check whether the user is locked. If the user is not locked, 3. Otherwise, the service is abnormal
- 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
- 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:
- Use excludePathPatterns directly when configuring interceptors to exclude requests that are not intercepted;
- In the interceptor to determine whether in the blacklist, in the direct release;
- 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
- Read the annotation on the control layer method to determine whether there is a release annotation, if there is no 2, otherwise release
- Check whether the token exists. If yes, 3. Otherwise, an exception occurs
- Check whether the token is valid. If the token is valid, 4 is displayed. Otherwise, the service is abnormal
- 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