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,會跳轉到登陸頁面

通過數據庫中的用戶登錄就可以得到結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM