springsecurity 权限管理实例菜单管理

项目需求

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

显示菜单原理

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

需求分析

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

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

技术选型

springboot:主要节省配置时间

springsecurity:用于认证和授权

MySQL:关系型数据管理

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

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

开发前期

数据准备

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

添加依赖

        <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下

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

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

基本开发

Mapper

根据用户名获取用户信息

<?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

 

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包扫描

@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

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

@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,会跳转到登陆页面

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

原文地址:https://www.cnblogs.com/WarBlog/p/15157431.html