springboot整合shiro实现简单权限控制

目录

一、为什么要了解权限框架

二、shiro介绍

三、环境准备

3.1 数据库表以及相关数据

3.2、shiro环境准备

四、自定义登录

第一步:需要先完成一些简单的配置:

第二步: 自定义登录方法

第三步:页面按钮隐藏

五、MD5加密算法加密解密过程

 过程中的一个报错:

传送门:


一、为什么要了解权限框架

        权限管理框架属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则用户可以访问而且只能访问自己被授权的资源。

        目前常见的权限框架有Shiro和Spring Security,本篇文章记录springboot整合shiro,实现简单的权限控制。

二、shiro介绍

        Shiro全称是Apache Shiro,是一款灵活、强大的安全框架。方便简洁的处理身份认证、授权、加密等。

        shiro的三个组件:

        Subject 【主体】:指代当前用户【人、爬虫等】

        SecurityManager【安全管理器】:管理所有的Subject、具体安全操作的真正执行者。

        Reamls:本质是一个安全的DTO,用于进行权限、认证信息;

        通过Subject来进行认证和授权,而Subject又委托给SecurityManager; 需要给Shrio的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。

三、环境准备

模拟场景【基于权限】:

                1、可以跳转到add页面,说明拥有add权限。

                2、可以跳转到update页面,说明拥有update权限。

                3、拥有add权限只展示add的链接、拥有update权限只展示update的链接;

模拟场景【基于角色】:

               1、拥有admin身份进入add、update,select页面,

               2、拥有user身份只可以进入select页面。

实现效果:

环境:

jdk 17

Maven 3.8.6

Mysql 8.x

IDEA2021

springboot 2.7.0

3.1 数据库表以及相关数据

        一共五张表:用户表、角色表、权限表、用户角色表、角色权限表。初始化SQL脚本在最后的传送门。

当前数据库数据:【用户->身份->权限】

        张三,角色是admin 拥有权限有add

        李四,角色是user,拥有权限有update

3.2、shiro环境准备

1、导入必要依赖、导入springboot-shiro的整合相关依赖依赖

        <!--  boot-shiro整合依赖-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.10.0</version>
        </dependency>

        <!--shiro缓存-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.7.1</version>
        </dependency>

        <!--   mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
    <!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

    <!-- hutool工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.11</version>
        </dependency>
    <!--thymeleaf模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.6.4</version>
        </dependency>
    <!--    thymeleaf-shiro整合依赖    -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2、配置文件的一一些必要参数

server:
  port: 8080
spring:
  thymeleaf:
    mode: HTML
    cache: false
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3308/boot_mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True
    username: root
    password: root

debug: true

mybatis:
  mapperLocations: mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #日志输出
    map-underscore-to-camel-case: true  #开启驼峰映射

3、 页面资源

         准备页面资源:index.html、login.html。启动项目来到首页、不用登录登录情况下可以访问任意页。

 项目目录结构:

四、自定义登录

第一步:需要先完成一些简单的配置:

1、新建UserReam类,继承AuthorizingRealm ,并重写他的认证和授权方法、实现自定义授权认证。

/**
 * 自定义UserRealm,用户认证授权登录
 * @author Alex
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;




    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token ) throws AuthenticationException {
        System.err.println("执行了+==========>认证AuthenticationInfo");
        // 用户名密码数据库里取
        UsernamePasswordToken userToken =(UsernamePasswordToken) token;
        IUser queryUser = new IUser();
        queryUser.setUserName(userToken.getUsername());

        List<IUser> userList = userService.selectUser(queryUser);


        if(CollectionUtils.isEmpty(userList)){
            return null;
        }else {
            IUser user = userList.get(0);
            System.err.println("user:"+user);

            // 密码认证 简单的equals比较
            return new SimpleAuthenticationInfo(user.getUserName(), user.getPassWord(),"");
        }

    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.err.println("执行了+==========>授权doGetAuthenticationInfo");
        String username = (String)principals.getPrimaryPrincipal();
        System.err.println("username"+username);
        IUser queryUser = new IUser();
        queryUser.setUserName(username);
//        根据用户名获取身份、再由身份获取权限
        List<IRole> roles = userService.selectRolesByUser(queryUser);
        if(CollectionUtils.isEmpty(roles)){
            return null;
        }else {
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            roles.forEach(role -> {
                simpleAuthorizationInfo.addRole(role.getName());
                //权限信息
                List<IPermission> perms = userService.selectPermsByRole(role);
                if (!CollectionUtils.isEmpty(perms)) {
                    perms.forEach(permission -> {
                        simpleAuthorizationInfo.addStringPermission(permission.getPermission());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
    }
}

2、新建ShiroConfiguration配置类,

        配置类里创建了工厂对象、安全对象、自定Ream等bean对象、  shiro内置了五个过滤器,可对资源、请求接口等进行拦截

/**
 * shiro配置类
 * @author Alex
 */
@Configuration
public class ShiroConfiguration {

    /**
     * 工厂对象3
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //给filter设置安全管理
        bean.setSecurityManager(defaultWebSecurityManager);


        /**
         *
         * 基于路径拦截资源
         *   anon 无需认证
         *   authc 必须认证
         *   user 记住我功能
         *   perms 拥有对某个资源
         *   roles 用某个角色权限
         */


        Map<String,String> map = new HashMap<>();
        map.put("/index","authc");
        map.put("/toLogin","anon");
        map.put("/","authc");
        map.put("/toAdd","perms[add]");
        map.put("/toUpdate","perms[update]");
        map.put("/toSelect", "roles[admin]");
        //更改默认的登录请求路径
        bean.setLoginUrl("/toLogin");
        //未授权请求路径
        bean.setUnauthorizedUrl("/unauthorized");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
    /**
     * 安全对象2
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 管理realm
        securityManager.setRealm(userRealm);
        return securityManager;

    }



    /**
     * 创建realm对象,先创建,再接管1
     */
    @Bean(name = "userRealm")
    public UserRealm userRealm(){
        return new UserRealm();
    }


    /**
     * 页面的Shiro标签生效
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

}

        shiro登录过程

                传统登录功能:

shiro拿到用户信息后。不是直接调用业务逻辑层的方法。现由SecurityUtils拿到登录对象、由对象取执行login方法,由于UserRealm 继承了AuthorizingRealm、所以登录操作被拦截、完成认证操作。login方法会抛出需要认证失败的异常。根据异常信息可以给前端对应的提示。

第二步: 自定义登录方法

 @PostMapping("/login")
    public String login(String username, String password, Model model){
        //获取用户主体对象
        Subject subject = SecurityUtils.getSubject();
        // 将输入的用户名+密码封装成一个token
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            model.addAttribute("userName",username);


            IUser user = new IUser();
            user.setUserName(username);
            List<IRole> roles = userService.selectRolesByUser(user);
            List<IPermission> perms = userService.selectPermsByRole(roles.get(0));
            model.addAttribute("role",roles.get(0).getName());
            model.addAttribute("perms",perms);

            return "index";
        }catch (UnknownAccountException e){
            e.printStackTrace();
            model.addAttribute("msg","用户名错误");
        }catch (IncorrectCredentialsException e){
            e.printStackTrace();
            model.addAttribute("msg","密码错误");
        }
        return "login";
    }

/**
 * 自定义UserRealm,用户认证授权登录
 * @author Alex
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token ) throws AuthenticationException {
        System.err.println("执行了+==========>认证AuthenticationInfo");
        SimpleAuthenticationInfo info =null;
        String username = token.getPrincipal().toString();
        IUser queryUser = new IUser();
        queryUser.setUserName(username);
        List<IUser> dbUserList = userService.selectUser(queryUser);
        if(CollectionUtils.isNotEmpty(dbUserList)){
            IUser dbUser = dbUserList.get(0);
                // 将注册时保存的随机盐构造ByteSource对象
             info = new SimpleAuthenticationInfo(dbUser.getUserName(),dbUser.getPassWord(),this.getName());
//             info = new SimpleAuthenticationInfo(dbUser.getUserName(),dbUser.getPassWord(),new SimpleByteSource(dbUser.getSalt()),this.getName());
        }
        return info;
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.err.println("执行了+==========>授权doGetAuthenticationInfo");
        String username = (String)principals.getPrimaryPrincipal();
        System.err.println("username"+username);
        IUser queryUser = new IUser();
        queryUser.setUserName(username);
//        根据用户名获取身份、再由身份获取权限
        List<IRole> roles = userService.selectRolesByUser(queryUser);
        if(CollectionUtils.isEmpty(roles)){
            return null;
        }else {
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            roles.forEach(role -> {
                simpleAuthorizationInfo.addRole(role.getName());
                //权限信息
                List<IPermission> perms = userService.selectPermsByRole(role);
                if (!CollectionUtils.isEmpty(perms)) {
                    perms.forEach(permission -> {
                        simpleAuthorizationInfo.addStringPermission(permission.getPermission());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
    }
}

完成了简单资源授权,为了前端展示,把对应的数据存到model

            IUser user = new IUser();
            user.setUserName(username);
            List<IRole> roles = userService.selectRolesByUser(user);
            List<IPermission> perms = userService.selectPermsByRole(roles.get(0));
            model.addAttribute("role",roles.get(0).getName());
            model.addAttribute("perms",perms);

 效果如下:

用户访问首页会被拦截、并跳转至登录页。

 

 以李四的账户登录情况也类似;

使用注解检查用户是否有可访问此接口

        @RequiresRoles

@RequiresRoles注解用于配置接口要求用户拥有某(些)角色才可访问,它拥有两个参数

参数类型描述
valueString[]角色列表
logicalLogical角色之间的判断关系,默认为Logical.AND

        @RequiresPermissions

@RequiresPermissions注解用于配置接口要求用户拥有某(些)权限才可访问,它拥有两个参数

参数类型描述
valueString[]权限列表
logicalLogical权限之间的判断关系,默认为Logical.AND

可单独使用、也可组合使用

比如:只有拥有add权限、拥有admin身份的主体才可以访问toAdd方法;

    @GetMapping("/toAdd")
    @RequiresPermissions("add")
    @RequiresRoles("admin")
    public String toAdd(){
        return "user/add";
    }

第三步:页面按钮隐藏

shiroConfig中创建bean对象

    /**
     * 页面的Shiro标签生效
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

页面引入头文件 

xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

使用

shiro:hasRole
shiro:hasPermission
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<h1>hello,shiro,我是<span th:text="${userName}"></span></h1>
<h1>权限有:</h1>
<div th:each="perm:${perms}">
    <div th:text="${perm.permission}"></div>
</div>
<hr>
<h1>身份是:</h1>
<h1 th:text="${role}"></h1>
<hr>

<h1>基于角色展示</h1>
<div shiro:hasRole="admin" >
    <a th:href="@{/toAdd}">新增</a><br>
    <a th:href="@{/toUpdate}">修改</a><br>
    <a th:href="@{/toSelect}">查询</a><br>
</div>

<div shiro:hasRole="user" >
    <a th:href="@{/toSelect}"><h1>查询</h1></a><br>
</div>


<h1>基于权限展示:</h1>

<div shiro:hasPermission="add">
    <a th:href="@{/toAdd}">新增</a><br>
</div>


<div shiro:hasPermission="update">
    <a th:href="@{/toUpdate}">修改</a><br>
</div>

<hr>
<a th:href="@{/toLogin}">登录</a><br>
<a th:href="@{/logout}">退出</a><br>


</body>
</html>

效果;

 

 以上本篇主体内容了

补充:为了用户信息安全、一般情况下都不会将 密码明文存入数据库,二是通过加密算法将明文加密后的字符串存入数据库、常见的加密算法有:

        单向散列加密算法:MD5;

        对称加密:

        非对称加密:

五、MD5加密算法加密解密过程

下面是对MD5+随机盐加密及解密过程的叙述。

MD5加密算法是hash算法的一种,原理是通过字符串通过MD5加密后得到一个消息摘要。

        通过MD5算法加密后的字符串本身是不可逆的,但是对同一个字符串加密后得到的密文是一致的、所以许多简单的密码可以用枚举实现解密。故加密时将明文+随机盐达到新的字符串尽量唯一从而增加安全性;

这里使用了一了开源hutools工具包生成随机数。hutools可以生成二维码、验证码等等,当然也可以自己实现。

    @Test
    public void testSlat(){

        //生成6位随机盐
        String salt = RandomUtil.randomString(6);
        System.err.println("salt:"+salt);
        //加密密码:原始密码+盐+Hash散列
        String MdsPwd = new Md5Hash("123", salt, 1024).toHex();
        //设置加密后的密码
        System.err.println(MdsPwd);

    }

 效果:

执行认证方法的时候把查到的随机盐带上:

将注册时保存的随机盐一并带上

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token ) throws AuthenticationException {
        System.err.println("执行了+==========>认证AuthenticationInfo");
        SimpleAuthenticationInfo info =null;
        String username = token.getPrincipal().toString();
        IUser queryUser = new IUser();
        queryUser.setUserName(username);
        List<IUser> dbUserList = userService.selectUser(queryUser);
        if(CollectionUtils.isNotEmpty(dbUserList)){
            IUser dbUser = dbUserList.get(0);
                // 将注册时保存的随机盐构造ByteSource对象
             info = new SimpleAuthenticationInfo(dbUser.getUserName(),dbUser.getPassWord(),new SimpleByteSource(dbUser.getSalt()),this.getName());
        }
        return info;
    }

 过程中的一个报错:

     

Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - 张三, rememberMe=false] did not match the expected credentials.
	at org.apache.shiro.realm.AuthenticatingRealm.assertCredentialsMatch(AuthenticatingRealm.java:603)

整体大意就是密码错误。

原因:是因为修改了shiro的认证策略后、获取UserRealm对象的方式还是以前的  

  new UserRealm(),这种方式比较密码是将输入的密码和数据库中密码进行简单的equals比较。而不是盐加+哈希散列的认证方式。所以只需要在获取userRealm对象的时候添加一下加密设置就好了。

1、配置类中注入CredentialsMatcher 对象。

    /**
     * 进行加密的设置
     */
    @Bean
    public CredentialsMatcher credentialsMatcher(){
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // 指定加密算法
        matcher.setHashAlgorithmName("md5");
        // 迭代次数
        matcher.setHashIterations(1024);
        return matcher;
    }

2、

    /**
     * 创建realm对象,先创建,再接管1
     */
    @Bean(name = "userRealm")
    public UserRealm userRealm(){
        UserRealm userRealm = new UserRealm();
        // 加密设置
        userRealm.setCredentialsMatcher(credentialsMatcher());;
        return userRealm;
    }

3、测试方法:

    @Autowired
    ShiroConfiguration configuration;
    
    @Test
    public void test(){
        // 创建安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        defaultSecurityManager.setRealm(configuration.userRealm());
        // 注入安全工具类
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // 拿到登录主体
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("张三", "123");

        try{
            subject.login(token);
            System.err.println("登录成功");
        }  catch (Exception e){
            System.err.println("登录失败");
            e.printStackTrace();

        }


    }

传送门:

初始化SQL脚本

使用IDEA新建一个springboot项目

springboot整合thymeleaf

springboot整合mybatis

springboot整合SpringSecurity并实现简单权限控制

  • 11
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值