由于用到了权限框架,所以架构重构还是有点注意项的
思路 :
把权限 相关的xml所实例化的bean 由代码是去实现下
AuthorizingRealm–》securityManager—》ShiroFilterFactoryBean
每一个去继承实现下,当然比如如果你需要认证的时候加密加盐
就可能需要注入相关子类
以Realm为例,现在我要用SHA256加密
可能在实现AuthorizingRealm的时候去实现 HashedCredentialsMatcher
package com.ect.bigdata.esync.account.security;
import com.ect.bigdata.esync.common.config.GlobalConfig;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
private Ehcache passwordRetryEhcache;
public RetryLimitHashedCredentialsMatcher() {
CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
passwordRetryEhcache = cacheManager.getCache("passwordRetryEhcache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String) token.getPrincipal();
//2.判断缓存中是否包含含有此用户名的Element
Element element = passwordRetryEhcache.get(username);
if(element!=null) {
element.setTimeToIdle(GlobalConfig.getLoginRetryLimitExpired());
}
//3.如果没有 ,就创建包含该用户名的Element并添加到缓存
if (element == null) {
element = new Element(username, new AtomicInteger(0));
passwordRetryEhcache.put(element);
}
//4.如果有 就通过该Element获取AutomicInteger
AtomicInteger atomicInteger = (AtomicInteger) element.getObjectValue();
if (GlobalConfig.isLoginRetryTimesLimit()) {
//5.如果AutomicInteger的incrementAndGet大于5 就抛异常
if (atomicInteger.incrementAndGet() > GlobalConfig.getLoginRetryTimes()) {
throw new ExcessiveAttemptsException();
}
}
//6.如果小于5 就调用父类的doCredentialsMatch方法进行密码验证
boolean matchs = super.doCredentialsMatch(token, info);
//7.如果验证通过,就将该username从缓存中清除
if (matchs) {
passwordRetryEhcache.remove(username);
}
return matchs;
}
}
@Configuration
public class ShiroConfig {
@Bean(name = "myRealm")
public SecurityDatabaseRealm myAuthRealm(RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher) {
SecurityDatabaseRealm myRealm = new SecurityDatabaseRealm();
retryLimitHashedCredentialsMatcher.setHashAlgorithmName("SHA-256");
retryLimitHashedCredentialsMatcher.setHashIterations(2);
retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
myRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher);
return myRealm;
}
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">
<diskStore path="java.io.tmpdir"/>
<!-- 登录记录缓存 锁定3分钟 -->
<cache name="passwordRetryEhcache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
</ehcache>
仔细看下代码和我们平时写的赋值操作是一样的
如果之间你直接封装好可以直接使用@Component
实例化下就可以了,然后在shiroConig中注入,废话不多说贴图
好了 以上解释仁者见仁,智者见智吧
步骤一:肯定先整认证的AuthorizingRealm
package com.ect.bigdata.esync.account.security;
import com.ect.bigdata.esync.account.AccountService;
import com.ect.bigdata.esync.account.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 实现 Shiro Realm ,用于获取认证信息与权限信息
*
*/
@Slf4j
@Component
public class SecurityDatabaseRealm extends AuthorizingRealm {
@Autowired
private AccountService accountService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authToken;
String username = token.getUsername();
if (username == null) {
throw new UnknownAccountException("Null usernames are not allowed by this realm.");
}
User user = accountService.findByUsername(username);
if (user == null) {
String msg = String.format("No account found for user [%s].", username);
log.warn(msg);
// throw new UnknownAccountException(msg);
return null;
}
// char[] password = token.getPassword();
// Set<String> userRoles = new HashSet<>();
// Set<String> rolePermissions = new HashSet<>();
// userRoles = accountService.getUserRoles(username);
// rolePermissions = accountService.getRolePermissions();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
// int salt = new Random().nextInt(2 << 30 -1);
String salt = user.getSalt();
log.debug("salt: {}", salt);
info.setCredentialsSalt(ByteSource.Util.bytes(salt));
return info;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) {
throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
}
String username = (String) getAvailablePrincipal(principals);
Set<String> roleNames = null;
Set<String> permissions = null;
try {
roleNames = accountService.getRoleNamesForUser(username);
permissions = accountService.getPermissions(roleNames);
} catch (Exception e) {
log.error("Exception getting authorization info!", e);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
info.setStringPermissions(permissions);
return info;
}
}
登录次数校验,以及实现何种加密方式
package com.ect.bigdata.esync.account.security;
import com.ect.bigdata.esync.common.config.GlobalConfig;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicInteger;
@Component
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
private Ehcache passwordRetryEhcache;
public RetryLimitHashedCredentialsMatcher() {
CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml"));
passwordRetryEhcache = cacheManager.getCache("passwordRetryEhcache");
}
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
String username = (String) token.getPrincipal();
//2.判断缓存中是否包含含有此用户名的Element
Element element = passwordRetryEhcache.get(username);
if(element!=null) {
element.setTimeToIdle(GlobalConfig.getLoginRetryLimitExpired());
}
//3.如果没有 ,就创建包含该用户名的Element并添加到缓存
if (element == null) {
element = new Element(username, new AtomicInteger(0));
passwordRetryEhcache.put(element);
}
//4.如果有 就通过该Element获取AutomicInteger
AtomicInteger atomicInteger = (AtomicInteger) element.getObjectValue();
if (GlobalConfig.isLoginRetryTimesLimit()) {
//5.如果AutomicInteger的incrementAndGet大于5 就抛异常
if (atomicInteger.incrementAndGet() > GlobalConfig.getLoginRetryTimes()) {
throw new ExcessiveAttemptsException();
}
}
//6.如果小于5 就调用父类的doCredentialsMatch方法进行密码验证
boolean matchs = super.doCredentialsMatch(token, info);
//7.如果验证通过,就将该username从缓存中清除
if (matchs) {
passwordRetryEhcache.remove(username);
}
return matchs;
}
}
重点来了,以下是如何引用核心配置
package com.ect.bigdata.esync.account.security;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean(name = "myRealm")
public SecurityDatabaseRealm myAuthRealm(RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher) {
SecurityDatabaseRealm myRealm = new SecurityDatabaseRealm();
retryLimitHashedCredentialsMatcher.setHashAlgorithmName("SHA-256");
retryLimitHashedCredentialsMatcher.setHashIterations(2);
retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
myRealm.setCredentialsMatcher(retryLimitHashedCredentialsMatcher);
return myRealm;
}
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("myRealm") SecurityDatabaseRealm myRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(myRealm);
return manager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String,String> filterMap = new LinkedHashMap<String, String>();
filterMap.put("/login/**","anon");
filterMap.put("/sys/version","anon");
filterMap.put("/merge/test","anon");
filterMap.put("/","authc");
filterMap.put("/monitor/**","authc");
filterMap.put("/profile/**","authc");
filterMap.put("/logout/**","authc");
filterMap.put("/merge/**","authc");
filterMap.put("/logout/**","roles[admin]");
filterMap.put("/dist/**","anon");
filterMap.put("/bower_components/**","anon");
filterMap.put("//plugins/**","anon");
filterMap.put("/css/**","anon");
filterMap.put("/js/**","anon");
filterMap.put("/img/**","anon");
//修改跳转的登陆页面
shiroFilterFactoryBean.setLoginUrl("/login");
//转跳至未授权页面【友好提示】
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
//跳转至成功页面
shiroFilterFactoryBean.setSuccessUrl("/monitor");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
}
控制器使用:
package com.ect.bigdata.esync.account;
import com.ect.bigdata.esync.account.entity.Role;
import com.ect.bigdata.esync.account.entity.User;
import com.ect.bigdata.esync.common.config.GlobalConfig;
import com.ect.bigdata.esync.common.util.LoginAesUtil;
import com.ect.bigdata.esync.common.util.PasswordEncryptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* 账户相关的控制器
* @author
*/
@Slf4j
@Controller
public class AccountController {
private static final String LOGIN_VIEW = "login";
private static final String CHANGE_CREDENTIAL_VIEW = "change-password";
private static final String USER_PROFILE_VIEW = "profile";
private static final String UNAUTHORIZED_VIEW = "unauthorized";
/**
* 使用Bootstrap的组件来展示 SpringMVC 的 Flash Attributes
*/
private static final String HTML_MESSAGE =
"<div id=\"flash-message\" class=\"alert alert-danger\" role=\"alert\">\n" +
" <strong>%s</strong>\n" +
"</div>";
/**
* 成功展示
*/
private static final String HTML_SUCCESS =
"<div id=\"flash-message\" class=\"alert alert-success\" role=\"alert\">\n" +
" <strong>%s</strong>\n" +
"</div>";
@Autowired
private AccountService accountService;
@RequestMapping(value = "/", method = RequestMethod.GET)
public String viewIndex() {
log.debug("Access to index view.");
return "redirect:/monitor";
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String viewLogin() {
log.debug("Access to login view.");
return LOGIN_VIEW;
}
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String submitLoginView(@RequestParam("username") String username, @RequestParam("password") String password,
Model model, RedirectAttributes redirectAttributes) throws Exception {
log.info("Login with username [{}] and password.", username);
final String FLASH_ALERT_MESSAGE = "errorLoginFlashMessage";
String validationFailureMessage="";
if(GlobalConfig.isLoginRetryTimesLimit()) {
validationFailureMessage = "账号或密码错误,您仅有" + GlobalConfig.getLoginRetryTimes() + "次尝试机会!";
}else {
validationFailureMessage = "账号或密码错误";
}
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
log.warn("Login with blank username or password, just ignore!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "账号或密码不能为空!"));
return LOGIN_VIEW;
}
// 前端加密密码还原
password = LoginAesUtil.aesDecrypt(password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
log.info("token -> {}", token);
try {
log.info("SecurityUtils.getSubject() -> {}", SecurityUtils.getSubject());
SecurityUtils.getSubject().login(token);
// 将用户及角色信息放入会话中
User user = accountService.findByUsername(username);
Set<String> roleNames = accountService.getRoleNamesForUser(username);
List<String> roleNameList = new ArrayList<>(roleNames);
List<Role> roleList = accountService.findRolesBy(roleNameList);
user.setRoleList(roleList);
List<String> roleDescriptionList = new ArrayList<>();
for (Role role : roleList) {
String roleDescription = role.getDescription();
roleDescriptionList.add(roleDescription);
}
SecurityUtils.getSubject().getSession().setAttribute("username", username);
SecurityUtils.getSubject().getSession().setAttribute("user", user);
SecurityUtils.getSubject().getSession().setAttribute("roleNamesOfUser", roleNameList);
SecurityUtils.getSubject().getSession().setAttribute("roleDescriptions", roleDescriptionList);
} catch (ExcessiveAttemptsException e) {
log.error("Login attempt more than 5 times, account enabled and disabled, see the configuration file for details", e);
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "登录认证失败超过5次,账号锁定" + ((int) (GlobalConfig.getLoginRetryLimitExpired() / 60)) + "分钟"));
return LOGIN_VIEW;
} catch (UnknownAccountException uae) {
log.warn("Authenticating failed due to UnknownAccountException!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, validationFailureMessage));
return LOGIN_VIEW;
} catch (IncorrectCredentialsException ice) {
log.warn("Authenticating failed due to IncorrectCredentialsException!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, validationFailureMessage));
return LOGIN_VIEW;
} catch (AuthenticationException e) {
log.error("Exception authenticating!", e);
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "登录认证失败,账号或密码错误!"));
return LOGIN_VIEW;
}
// 重定向时,不能使用 Model 来传值,因为 Model 中的一切都会在重定向经过客户端浏览器时丢失!
redirectAttributes.addFlashAttribute("message", "The user is logged in successfully!");
log.info("Login success, redirect monitor page!");
return "redirect:/monitor/";
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(@RequestParam("username") String username,
Model model) throws Exception {
SecurityUtils.getSubject().logout();
log.info("SecurityUtils.getSubject().getSession().getAttributeKeys() -> {}", SecurityUtils.getSubject().getSession().getAttributeKeys());
log.info("SecurityUtils.getSubject().getSession().getAttribute(username) -> {}", SecurityUtils.getSubject().getSession().getAttribute("username"));
log.info("SecurityUtils.getSubject().getPrincipal() -> {}", SecurityUtils.getSubject().getPrincipal());
log.info("Current user [{}] logout successfully!", username);
return "redirect:/login";
}
@RequestMapping(value = "/change-password", method = RequestMethod.GET)
public String viewChangePassword() throws Exception {
log.info("Access to CHANGE_CREDENTIAL_VIEW.");
return CHANGE_CREDENTIAL_VIEW;
}
@RequestMapping(value = "/change-password", method = RequestMethod.POST)
public String submitChangePasswordView(@RequestParam("username") String username,
@RequestParam("oldPassword") String oldPassword,
@RequestParam("newPassword") String newPassword,
Model model, RedirectAttributes redirectAttributes) throws Exception {
log.info("Submitting CHANGE_CREDENTIAL_VIEW with username [{}].", username);
final String FLASH_ALERT_MESSAGE = "flashAlertMessage";
if (StringUtils.isBlank(username) || StringUtils.isBlank(oldPassword) || StringUtils.isBlank(newPassword) ) {
log.warn("Ignore when inputting blank account/oldPassword/newPassword!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "账号、原密码或新密码不能为空!"));
return CHANGE_CREDENTIAL_VIEW;
}
User user = accountService.findByUsername(username);
// 前端加密密码还原
oldPassword = LoginAesUtil.aesDecrypt(oldPassword);
newPassword = LoginAesUtil.aesDecrypt(newPassword);
if (newPassword.equals(oldPassword)) {
log.warn("Old password and new password cannot be equal!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "新密码不能和原密码相同!"));
return CHANGE_CREDENTIAL_VIEW;
}
if (newPassword.length() < 8 || newPassword.length() > 10) {
log.warn("New password length should be 8~10!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "新密码长度应该为8~10位!"));
return CHANGE_CREDENTIAL_VIEW;
}
// 对旧密码进行加密 用于对比数据库密码
oldPassword = new PasswordEncryptor().encrypt(oldPassword,user.getSalt());
if (user == null || !oldPassword.equals(user.getPassword())) {
log.warn("Invalid account or incorrect old password!");
model.addAttribute(FLASH_ALERT_MESSAGE, String.format(HTML_MESSAGE, "账号或原密码错误!"));
return CHANGE_CREDENTIAL_VIEW;
}
// 对新密码进行加密
newPassword = new PasswordEncryptor().encrypt(newPassword,user.getSalt());
user.setPassword(newPassword);
try {
accountService.saveUser(user);
} catch (Exception e) {
String errorMsg = String.format("Exception changing password for user [%s]!", username);
log.error(errorMsg, e);
throw new Exception(errorMsg);
}
log.info("Change password for user [{}] successfully.", username);
redirectAttributes.addFlashAttribute("successFlashMessage", String.format(HTML_SUCCESS, "密码更新成功"));
return "redirect:login";
}
@RequestMapping(value = "/profile", method = RequestMethod.GET)
public String viewUserProfile() throws Exception {
log.info("Access to USER_PROFILE_VIEW.");
// TODO
// 展示当前用户信息:用户名、角色列表、简介(包含姓名、部门、团队、邮箱、电话等)
return USER_PROFILE_VIEW;
}
@RequestMapping(value = "/profile2", method = RequestMethod.GET)
public String viewUserProfile2() throws Exception {
log.info("Access to USER_PROFILE_VIEW.");
return "profile2";
}
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public String submitUserProfileView() throws Exception {
log.info("Submitting USER_PROFILE_VIEW.");
// TODO
return USER_PROFILE_VIEW;
}
@RequestMapping(value = "/unauthorized", method = RequestMethod.GET)
public String viewUnauthorized() throws Exception {
log.info("Access to UNAUTHORIZED_VIEW.");
// TODO
return UNAUTHORIZED_VIEW;
}
}
数据库建表:
/*
SQLyog 企业版 - MySQL GUI v8.14
MySQL - 5.5.59-MariaDB : Database - tmpdb
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
/*Table structure for table `cdep_permission` */
DROP TABLE IF EXISTS `cdep_permission`;
CREATE TABLE `cdep_permission` (
`id` bigint(20) NOT NULL,
`permission` varchar(50) NOT NULL COMMENT '权限名',
`resource` varchar(1000) DEFAULT NULL COMMENT '权限资源值',
`create_by` varchar(15) DEFAULT NULL COMMENT '创建者',
`update_by` varchar(15) DEFAULT NULL COMMENT '修改者',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`update_at` datetime DEFAULT NULL COMMENT '更新时间',
`valid` int(11) DEFAULT '1' COMMENT '是否有效 (1有效, 0无效),用于逻辑删除',
`version` int(11) DEFAULT NULL COMMENT '版本,用于乐观锁',
PRIMARY KEY (`id`),
UNIQUE KEY `permission` (`permission`),
KEY `ix_cp_permission` (`permission`),
KEY `ix_cp_update_at` (`update_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CDEP权限表';
/*Table structure for table `cdep_role` */
DROP TABLE IF EXISTS `cdep_role`;
CREATE TABLE `cdep_role` (
`id` bigint(20) NOT NULL,
`role_name` varchar(50) NOT NULL COMMENT '角色名',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`create_by` varchar(15) DEFAULT NULL COMMENT '创建者',
`update_by` varchar(15) DEFAULT NULL COMMENT '修改者',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`update_at` datetime DEFAULT NULL COMMENT '更新时间',
`valid` int(11) DEFAULT '1' COMMENT '是否有效 (1有效, 0无效),用于逻辑删除',
`version` int(11) DEFAULT NULL COMMENT '版本,用于乐观锁',
PRIMARY KEY (`id`),
UNIQUE KEY `role_name` (`role_name`),
KEY `ix_cr_role_name` (`role_name`),
KEY `ix_cr_update_at` (`update_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CDEP角色表';
/*Table structure for table `cdep_role_permission` */
DROP TABLE IF EXISTS `cdep_role_permission`;
CREATE TABLE `cdep_role_permission` (
`id` bigint(20) NOT NULL,
`role_id` varchar(32) DEFAULT NULL,
`permission_id` varchar(32) DEFAULT NULL,
`create_by` varchar(15) DEFAULT NULL COMMENT '创建者',
`update_by` varchar(15) DEFAULT NULL COMMENT '修改者',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`update_at` datetime DEFAULT NULL COMMENT '更新时间',
`valid` int(11) DEFAULT '1' COMMENT '是否有效 (1有效, 0无效),用于逻辑删除',
`version` int(11) DEFAULT NULL COMMENT '版本,用于乐观锁',
PRIMARY KEY (`id`),
KEY `ix_crp_role_id` (`role_id`),
KEY `ix_crp_permission_id` (`permission_id`),
KEY `ix_crp_update_at` (`update_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CDEP角色权限关联表';
/*Table structure for table `cdep_user` */
DROP TABLE IF EXISTS `cdep_user`;
CREATE TABLE `cdep_user` (
`id` bigint(20) NOT NULL,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`salt` varchar(100) DEFAULT NULL COMMENT '盐值,用于密码加密',
`enabled` int(11) DEFAULT '1' COMMENT '是否启用 (1启用, 0禁用)',
`locked` int(11) DEFAULT '0' COMMENT '是否锁定 (1是, 0否)',
`expired` int(11) DEFAULT '0' COMMENT '是否过期 (1是, 0否)',
`description` varchar(255) DEFAULT NULL COMMENT '描述',
`create_by` varchar(15) DEFAULT NULL COMMENT '创建者',
`update_by` varchar(15) DEFAULT NULL COMMENT '修改者',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`update_at` datetime DEFAULT NULL COMMENT '更新时间',
`valid` int(11) DEFAULT '1' COMMENT '是否有效 (1有效, 0无效),用于逻辑删除',
`version` int(11) DEFAULT NULL COMMENT '版本,用于乐观锁',
PRIMARY KEY (`id`),
KEY `ix_cu_username` (`username`),
KEY `ix_cu_update_at` (`update_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CDEP用户表';
/*Table structure for table `cdep_user_role` */
DROP TABLE IF EXISTS `cdep_user_role`;
CREATE TABLE `cdep_user_role` (
`id` bigint(20) NOT NULL,
`user_id` varchar(32) DEFAULT NULL,
`role_id` varchar(32) DEFAULT NULL,
`create_by` varchar(15) DEFAULT NULL COMMENT '创建者',
`update_by` varchar(15) DEFAULT NULL COMMENT '修改者',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`update_at` datetime DEFAULT NULL COMMENT '更新时间',
`valid` int(11) DEFAULT '1' COMMENT '是否有效 (1有效, 0无效),用于逻辑删除',
`version` int(11) DEFAULT NULL COMMENT '版本,用于乐观锁',
PRIMARY KEY (`id`),
KEY `ix_cur_user_id` (`user_id`),
KEY `ix_cur_role_id` (`role_id`),
KEY `ix_cur_update_at` (`update_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CDEP用户角色关联表';
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;