项目需求

根据用户权限不同显示不同菜单,admin显示菜单(用户管理、角色管理、菜单管理),guest显示菜单(用户管理、菜单管理)

显示菜单原理

向客户端返回不同的菜单数据

需求分析

数据结构:用户表、角色表、用户角色关系表、菜单表、菜单角色关系表。

项目业务逻辑:通过登录用户名获取用户对象,并根据用户对象的Id属性获取菜单信息

技术选型

springboot:主要节省配置时间

springsecurity:用于认证和授权

MySQL:关系型数据管理

mybatis-plus(或mybatis):为了使用自动生成实体类

Lombok:结合mybatis-plus,免去手动编写实体类的get、set方法

开发前期

数据准备


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


CREATE TABLE `t_menu` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(30) DEFAULT NULL,
`href` VARCHAR(50) DEFAULT NULL,
`isEnable` TINYINT(1) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3;

/*Data for the table `t_menu` */

INSERT INTO `t_menu`(`id`,`name`,`href`,`isEnable`) VALUES
(1,'用户管理','users',1),
(2,'角色管理','roles',1),
(3,'菜单管理','menus',1);

/*Table structure for table `t_role` */

CREATE TABLE `t_role` (
`id` INT NOT NULL AUTO_INCREMENT,
`role` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3;

/*Data for the table `t_role` */

INSERT INTO `t_role`(`id`,`role`) VALUES
(1,'管理员'),
(2,'宾客');

/*Table structure for table `t_role_menu` */

CREATE TABLE `t_role_menu` (
`role_Id` INT NOT NULL,
`menu_Id` INT NOT NULL,
PRIMARY KEY (`role_Id`,`menu_Id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb3;

/*Data for the table `t_role_menu` */

INSERT INTO `t_role_menu`(`role_Id`,`menu_Id`) VALUES
(1,1),
(1,2),
(1,3),
(2,1),
(2,3);

/*Table structure for table `t_role_user` */

CREATE TABLE `t_role_user` (
`user_Id` INT NOT NULL,
`role_Id` INT NOT NULL,
PRIMARY KEY (`user_Id`,`role_Id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb3;

/*Data for the table `t_role_user` */

INSERT INTO `t_role_user`(`user_Id`,`role_Id`) VALUES
(1,1),
(2,2);

/*Table structure for table `t_user` */

CREATE TABLE `t_user` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) DEFAULT NULL,
`password` VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3;

/*Data for the table `t_user` */

INSERT INTO `t_user`(`id`,`username`,`password`) VALUES
(1,'admin','123'),
(2,'guest','123');

View Code

添加依赖


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>

<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

View Code

使用mybatis-plus-generator生成基础架构

这个代码不是项目代码所以放在了test下


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


public class CodeGenerator {

// 数据库 URL
private static final String URL = "jdbc:mysql://192.168.223.129:3306/springbootdb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC";
// 数据库驱动
private static final String DRIVER_NAME = "com.mysql.cj.jdbc.Driver";
// 数据库用户名
private static final String USERNAME = "root";
// 数据库密码
private static final String PASSWORD = "root";
// @author 值
private static final String AUTHOR = "marw";
// 包的基础路径
private static final String BASE_PACKAGE_URL = "com.marw.sys";
// xml文件路径
private static final String XML_PACKAGE_URL = "/src/main/resources/mapper/";
// xml 文件模板
private static final String XML_MAPPER_TEMPLATE_PATH = "/templates/mapper.xml";
// mapper 文件模板
private static final String MAPPER_TEMPLATE_PATH = "/templates/mapper.java";
// entity 文件模板
private static final String ENTITY_TEMPLATE_PATH = "/templates/entity.java";
// service 文件模板
private static final String SERVICE_TEMPLATE_PATH = "/templates/service.java";
// serviceImpl 文件模板
private static final String SERVICE_IMPL_TEMPLATE_PATH = "/templates/serviceImpl.java";
// controller 文件模板
private static final String CONTROLLER_TEMPLATE_PATH = "/templates/controller.java";

public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();

// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
globalConfig.setOutputDir(projectPath + "/src/main/java");
globalConfig.setAuthor(AUTHOR);
globalConfig.setOpen(false);
globalConfig.setFileOverride(false);
generator.setGlobalConfig(globalConfig);

// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl(URL);
dataSourceConfig.setDriverName(DRIVER_NAME);
dataSourceConfig.setUsername(USERNAME);
dataSourceConfig.setPassword(PASSWORD);
generator.setDataSource(dataSourceConfig);

// 包配置
PackageConfig packageConfig = new PackageConfig();
//packageConfig.setModuleName("gen");
packageConfig.setParent(BASE_PACKAGE_URL);
generator.setPackageInfo(packageConfig);


// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";

// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + XML_PACKAGE_URL + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});

cfg.setFileOutConfigList(focList);
generator.setCfg(cfg);

// 配置自定义代码模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);




templateConfig.setMapper(MAPPER_TEMPLATE_PATH);
templateConfig.setEntity(ENTITY_TEMPLATE_PATH);
templateConfig.setService(SERVICE_TEMPLATE_PATH);
templateConfig.setServiceImpl(SERVICE_IMPL_TEMPLATE_PATH);
templateConfig.setController(CONTROLLER_TEMPLATE_PATH);
generator.setTemplate(templateConfig);

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
//strategy.setInclude(scanner("表名"));
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
//strategy.setSuperEntityColumns("id");
//驼峰转换
strategy.setControllerMappingHyphenStyle(true);
//strategy.setTablePrefix(packageConfig.getModuleName() + "_");
strategy.setTablePrefix("t_");
generator.setStrategy(strategy);
generator.setTemplateEngine(new FreemarkerTemplateEngine());
generator.execute();
}

private static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
System.out.println(("请输入" + tip + ":"));
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) return ipt;
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
}

View Code

生成后的结果也做了修改,效果如下:

springsecurity 权限管理实例菜单管理_数据库_07

基本开发

Mapper

根据用户名获取用户信息


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.marw.sys.mapper.UserMapper">
<select id="findUserByUsername" resultType="com.marw.sys.entity.User">
select * from t_user where username=#{username}
</select>
</mapper>

View Code

根据用户Id获取菜单信息,菜单中需要角色信息



@Data
public class MenuExtend extends Menu{
private String roleId;
private String roleName;
}


 



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.marw.sys.mapper.MenuMapper">
<select id="findMenuByUsreId" resultType="com.marw.sys.entity.MenuExtend">
select tm.*,r.id roleId,r.role roleName
from t_menu tm
inner join t_role_menu trm on tm.id=trm.menu_Id
inner join t_role r on trm.role_id=r.id
inner join t_role_user ru on trm.role_Id=ru.role_Id
where ru.user_Id=#{id}
</select>
</mapper>


application.yaml

 


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


server:
port: 8083

spring:
datasource:
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.223.129:3306/springbootdb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useAffectedRows=true

mybatis-plus:
type-aliases-package: com.marw.domain
mapper-locations: classpath:mapper/*.xml
configuration:
jdbc-type-for-null: null
global-config:
banner: false

View Code

修改springboot启动类

添加Mapper包扫描


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


@SpringBootApplication
@MapperScan("com.marw.sys.mapper")
public class SpringSecurityWebApplication {

public static void main(String[] args) {
SpringApplication.run(SpringSecurityWebApplication.class, args);
}

}

View Code

认证开发

springsecurity认证,通过实现UserDetailsService接口中loadUserByUsername方法从数据库获取数据对用户进行认证,认证结果存储在实现UserDetails接口的对象中,其对象需要包含了用户信息和菜单信息(菜单信息是我们需要的)

UserDetails实现类SecurityUser


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


public class SecurityUser implements UserDetails {

private User user;
private List<MenuExtend> menuList;

public User getUser() {
return user;
}

public List<MenuExtend> getMenuList() {
return menuList;
}

public SecurityUser(User user, List<MenuExtend> menuList) {
this.user = user;
this.menuList = menuList;
}

/**
* 菜单中需要角色信息就是为了这个方法
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (MenuExtend item : this.menuList) {
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(item.getRoleName());
authorities.add(simpleGrantedAuthority);
}
return authorities;
}

@Override
public String getPassword() {
return user.getPassword();
}

@Override
public String getUsername() {
return user.getUsername();
}

@Override
public boolean isAccountNonExpired() {
return true;//数据库中没有这个字段直接设置true
}

@Override
public boolean isAccountNonLocked() {
return true;//数据库中没有这个字段直接设置true
}

@Override
public boolean isCredentialsNonExpired() {
return true;//数据库中没有这个字段直接设置true
}

@Override
public boolean isEnabled() {
return true;//数据库中没有这个字段直接设置true
}
}

View Code

UserDetailsService实现类UserDetailsServiceImpl


springsecurity 权限管理实例菜单管理_数据库springsecurity 权限管理实例菜单管理_xml_02


@Component
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private PasswordEncoder passwordEncoder;

@Autowired
private UserMapper userMapper;
@Autowired
private MenuMapper menuMapper;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据username获取user信息
com.marw.sys.entity.User user=userMapper.findUserByUsername(username);

if(user==null){
throw new UsernameNotFoundException("用户不存在");
}

user.setPassword(passwordEncoder.encode(user.getPassword()));
//根据UserID获取菜单
List<MenuExtend> menuList = menuMapper.findMenuByUsreId(user.getId());

SecurityUser securityUser = new SecurityUser(user, menuList);
return securityUser;
}
}

View Code

设置认证



@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsServiceImpl;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//指定认证和加密方式
auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder());
}

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}


测试Controller

获取springsecurity认证的数据是通过SecurityContextHolder获取的



@RestController
public class IndexController {
@GetMapping("/index")
public String index(){
return "index";
}

@GetMapping("/permission")
@ResponseBody
public Map<String,Object> getPermisiion(){
SecurityUser user = (SecurityUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
List<MenuExtend> menuList = user.getMenuList();
Map<String,Object> data = new HashMap<>();
data.put("list",menuList);
return data;
}
}


访问http://localhost:8083/permission,会跳转到登陆页面

springsecurity 权限管理实例菜单管理_java_18

 

通过数据库中的用户登录就可以得到结果:

springsecurity 权限管理实例菜单管理_数据库_19