SpringBoot 整合 Shiro

SpringBoot 整合 Shiro

可对比 SpringSecurity 学习使用。

1、环境搭建

  1. 创建 springboot 的 web 项目,创建时勾选 thymeleaf 选项

    也可以导入直接创建 web 项目,然后导入 thymeleaf 依赖

    <!--引入thymeleaf依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
  2. 编写首页

    • 导入命名空间 xmlns:th="http://www.thymeleaf.org"
    • 获取测试数据 <p th:text="${msg}"></p>
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
    
    <h1>首页</h1>
    
    <p th:text="${msg}"></p>
    
    </body>
    </html>
  3. 编写对应的 controller

    package com.cheng.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class MyController {
    
        @RequestMapping({"/", "/index", "/index.html"})
        public String toIndex(Model model) {
            model.addAttribute("msg", "Hello springboot-shiro!");
            return "index";
        }
    }
  4. 测试

    测试成功!

  5. 导入 shiro-spring 依赖

    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.5.3</version>
    </dependency>
  6. 自定义 UserRealm 类

    package com.cheng.config;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    public class UserRealm extends AuthorizingRealm {
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了授权 ==> doGetAuthorizationInfo 方法");
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("执行了认证 ==> doGetAuthenticationInfo 方法");
            return null;
        }
    }
  7. 配置 ShiroConfig

    package com.cheng.config;
    
    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;
    
    @Configuration
    public class ShiroConfig {
    
        // 3.ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            bean.setSecurityManager(defaultWebSecurityManager);
            return bean;
        }
    
        // 2.DefaultWebSecurityManager
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 关联 UserRealm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
    
        // 1.创建 realm 对象,需要自定义类
        @Bean
        public UserRealm userRealm() {
            return new UserRealm();
        }
    }
  8. 在 templates 文件夹下创建 user 文件夹,在 user 文件夹下创建 添加用户页 和 修改用户页

    • templates
      • user
        • addUser
        • updateUser
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>addUser</title>
    </head>
    <body>
    
    <h2>添加用户页</h2>
    
    </body>
    </html>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>updateUser</title>
    </head>
    <body>
    
    <h2>修改用户页</h2>
    
    </body>
    </html>
  9. 编写 添加用户 和 修改用户 的 controller

    @RequestMapping("/user/add")
    public String toAddUserPage() {
        return "user/addUser";
    }
    
    @RequestMapping("/user/update")
    public String toUpdateUserPage() {
        return "user/updateUser";
    }
  10. 在首页添加跳转到 添加用户页 和 修改用户页 的链接

    <hr>
    
    <a th:href="@{/user/add}">添加用户</a> | <a th:href="@{/user/update}">修改用户</a>
  11. 测试

    测试成功!

2、Shiro 实现登录拦截

  1. 添加 shiro 的内置过滤器

    // 3.ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(defaultWebSecurityManager);
    
        // 添加 shiro 的内置过滤器
        /*
        * anon : 无需认证就可以访问
        * authc : 必须认证了才能访问
        * user : 必须拥有 记住我 功能才能访问
        * perms : 拥有对某个资源的权限才能访问
        * roles : 拥有某个角色权限才能访问
        * */
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/add", "authc");
        filterMap.put("/user/update", "authc");
        bean.setFilterChainDefinitionMap(filterMap);
        
        return bean;
    }
  2. 编写登录页

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录页</title>
    </head>
    <body>
    
    <h2>登录页</h2>
    <hr>
    
    <form action="">
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="password" name="password"></p>
        <p><input type="submit" value="提交"></p>
    </form>
    
    </body>
    </html>
  3. 在 controller 中配置登录页

    @RequestMapping("/toLogin")
    public String toLogin() {
        return "login";
    }
  4. 在 config 的 ShiroFilterFactoryBean 中设置登录的请求

    // 3.ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(defaultWebSecurityManager);
    
        // 添加 shiro 的内置过滤器
        /*
        * anon : 无需认证就可以访问
        * authc : 必须认证了才能访问
        * user : 必须拥有 记住我 功能才能访问
        * perms : 拥有对某个资源的权限才能访问
        * roles : 拥有某个角色权限才能访问
        * */
    
        Map<String, String> filterMap = new LinkedHashMap<>();
        //filterMap.put("/user/add", "authc");
        //filterMap.put("/user/update", "authc");
        filterMap.put("/user/*", "authc");
        bean.setFilterChainDefinitionMap(filterMap);
    
        // 设置登录的请求
        bean.setLoginUrl("/toLogin");
    
        return bean;
    }
  5. 测试

    测试成功!

3、Shiro 实现用户认证

  1. 在 controller 中配置 登录认证

    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        // 获取当前的用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登陆数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    
        try {// 没有异常,登录成功,返回首页
            subject.login(token);// 执行登录的方法
            return "index";
        }catch (UnknownAccountException e) {// 用户名不存在,返回登陆页
            model.addAttribute("msg", "用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e) {// 密码不存在,返回登陆页
            model.addAttribute("msg", "密码错误");
            return "login";
        }
    
    }
  2. 在 login 页面设置提交,并设置错误信息展示

    <p th:text="${msg}" style="color: red"></p>
    <form th:action="@{/login}">
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="password" name="password"></p>
        <p><input type="submit" value="提交"></p>
    </form>
  3. 在 UserRealm 中的认证方法中,进行用户名和密码校验

    注:这里先伪造数据进行测试

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了认证 ==> doGetAuthenticationInfo 方法");
    
        // 用户名,密码 从数据库中取
        String name = "root";
        String password = "123456";
    
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
    
        if (!userToken.getUsername().equals(name)) {// 用户名不存在
            return null;
        }
    
        // 密码认证,shiro 去做
        return new SimpleAuthenticationInfo("",password, "");
    }
  4. 重启测试

    测试成功:

    • 用户名输入 root 之外的字符时,提交显示用户名错误信息
    • 密码输入 123456 之外的字符时,提交显示密码错误信息
    • 用户名和密码输入正确时,登陆成功

4、Shiro 整合 Mybatis

  1. 导入数据库连接所需要的依赖

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.22</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    
    <!--偷懒神器,非必要-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
  2. 配置 ymal

    spring:
      datasource:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        #Spring Boot 默认是不注入这些属性值的,需要自己绑定
        #druid 数据源专有配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
    
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
  3. 连接数据库,搭建框架(pojo, mapper),配置 properties

    mybatis.type-aliases-package=com.cheng.pojo
    mybatis.mapper-locations=classpath:mapper/*.xml
    
  4. 编写 User 实体类

    package com.cheng.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
  5. 编写 UserMapper 接口

    package com.cheng.mapper;
    
    import com.cheng.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Repository
    @Mapper
    public interface UserMapper {
    
        public User queryUserByName(String name);
    
    }
  6. 编写 UserMapper.xml 配置文件

    <?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.cheng.mapper.UserMapper">
    
        <select id="queryUserByName" parameterType="String" resultType="User">
            select * from mybatis.user where name=#{name};
        </select>
    
    </mapper>
  7. 编写 UserService 接口

    package com.cheng.service;
    
    import com.cheng.pojo.User;
    
    public interface UserService {
    
        public User queryUserByName(String name);
    }
  8. 编写 UserServiceImpl 实现类

    package com.cheng.service;
    
    import com.cheng.mapper.UserMapper;
    import com.cheng.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        UserMapper userMapper;
        
        @Override
        public User queryUserByName(String name) {
            return userMapper.queryUserByName(name);
        }
    }
  9. 测试

    测试成功!

  10. 将 UserRealm 认证中的模拟数据修改为真实数据

    package com.cheng.config;
    
    import com.cheng.pojo.User;
    import com.cheng.service.UserService;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        UserService userService;
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了授权 ==> doGetAuthorizationInfo 方法");
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了认证 ==> doGetAuthenticationInfo 方法");
    
            UsernamePasswordToken userToken = (UsernamePasswordToken) token;
    
            // 连接真实数据库,获取用户
            User user = userService.queryUserByName(userToken.getUsername());
    
            if (user == null) {// 没有查到这个人
                return null;
            }
    
            // 密码认证,shiro 去做
            return new SimpleAuthenticationInfo("",user.getPwd(), "");
        }
    }
  11. 测试,使用数据库中的用户名及密码登录

    测试成功!

5、Shiro 请求授权实现

  1. 设置 /user/add 需要有权限才能进入

    // 3.ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            bean.setSecurityManager(defaultWebSecurityManager);
    
            // 添加 shiro 的内置过滤器
            /*
            * anon : 无需认证就可以访问
            * authc : 必须认证了才能访问
            * user : 必须拥有 记住我 功能才能访问
            * perms : 拥有对某个资源的权限才能访问
            * roles : 拥有某个角色权限才能访问
            * */
    
            // 拦截
            Map<String, String> filterMap = new LinkedHashMap<>();
    
            // 授权,正常情况下,未授权会跳转到未授权页面
            // 注:如果拦截使用了 /user/* , 那么首选需要写在拦截之前,否则不生效
            filterMap.put("/user/add", "perms[user:add]");
            filterMap.put("/user/update", "perms[user:update]");
    
            // 拦截,需要认证了才能访问
            //filterMap.put("/user/add", "authc");
            //filterMap.put("/user/update", "authc");
            filterMap.put("/user/*", "authc");
    
    
            bean.setFilterChainDefinitionMap(filterMap);
    
    
            // 设置登录的请求
            bean.setLoginUrl("/toLogin");
    
            return bean;
        }

    注:经测试发现👇

    • 如果拦截使用了 /user/* , 那么授权需要写在拦截之前,否则不生效
    • 如果拦截使用了具体的 /user/add 和 /user/update(注释部分),则 授权 与 拦截 顺序无影响
  2. 重启测试

    • 当登录的用户没有权限时,无法进入 /user/add 页面,显示 401 错误(未授权错误)。
    • /user/update 页面则不需要权限,即可以进入

    测试成功!

  3. 设置未授权页面

    • 在 controller 中配置

      @RequestMapping("/noauth")
      @ResponseBody
      public String unauthorized() {
          // 注:正常情况下,这里会跳转到一个未授权页面,这里仅做测试
          return "用户未经授权,无法访问此页面!";
      }
    • 在 config 中配置

      // 设置未授权页面
      bean.setUnauthorizedUrl("/noauth");
  4. 测试

    登陆后进入 add 页面,显示设置的 ”用户未经授权,无法访问此页面!” 信息,测试成功!

  5. 设置权限

    • 在数据库中添加权限列(perms),并设置测试值

      • 张三: 有 user/add 权限
      • 李四:有 user/update 权限
    • 修改 pojo 文件夹下的 User 实体类

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class User {
          private int id;
          private String name;
          private String pwd;
          private String perms;
      }
    • 修改认证中的密码认证时,传递 user,即 SimpleAuthenticationInfo 传递的第一个参数

      // 认证
      @Override
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
          System.out.println("执行了认证 ==> doGetAuthenticationInfo 方法");
      
          UsernamePasswordToken userToken = (UsernamePasswordToken) token;
      
          // 连接真实数据库,获取用户
          User user = userService.queryUserByName(userToken.getUsername());
      
          if (user == null) {// 没有查到这个人
              return null;
          }
      
          // 密码认证,shiro 去做
          return new SimpleAuthenticationInfo(user,user.getPwd(), "");
      }
    • 在授权中拿到当前的 user ,并设置当前用户的权限。

      // 授权
      @Override
      protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
          System.out.println("执行了授权 ==> doGetAuthorizationInfo 方法");
      
          SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
          // 设置所有用户都能拥有的权限
          // info.addStringPermission("user:add");
      
          // 拿到当前登录的这个对象
          Subject subject = SecurityUtils.getSubject();
          User currentUser = (User) subject.getPrincipal();// 拿到User对象
      
          // 设置当前用户的权限
          info.addStringPermission(currentUser.getPerms());
      
          return info;
      }
  6. 重启测试

    • 当用户 张三 登录时,能进入 add 页面(有权限),无法进入 update 页面(无权限)
    • 当用户 李四 登录时,不能进入 add 页面(无权限),能进入 update 页面(有权限)

    测试成功!

6、Shiro 整合 Thymeleaf

  1. 导入整合所需要的

    <!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
  2. 在 Config 中配置

    // 整合 ShiroDialect : 用来整合 Shiro Thymeleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        return  new ShiroDialect();
    }
  3. 修改 index.html 页面,根据不同权限显示不同内容

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
        xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
    
    <h1>首页</h1>
    
    <p th:text="${msg}"></p>
    <hr>
    
    <!--游客访问
    未登陆时:有登录链接
    登陆后(非游客):不显示登录链接-->
    <div shiro:guest>
        <a th:href="@{/toLogin}">登录</a>
    </div>
    
    <!--有 user:add 权限时,显示 添加用户 链接-->
    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">添加用户</a>
    </div>
    
    <!--有 user:update 权限时,显示 修改用户 链接-->
    <div shiro:hasPermission="user:update">
        <a th:href="@{/user/update}">修改用户</a>
    </div>
    
    </body>
    </html>
  4. 测试

    测试成功!

补充:

权限标签

<!--游客访问
未登陆时:有登录链接
登陆后(非游客):不显示登录链接-->
<div shiro:guest>
    <a th:href="@{/toLogin}">登录</a>
</div>

<!--
user 标签:用户已通过认证\记住我\登录后显示响应的内容
-->
<div shiro:user>
    <p th:text="${msg}"></p>
</div>

<!--
authenticated 标签:用户身份验证通过,即 Subjec.login 登录成功 不是记住我登录的
-->
<div shiro:authenticated>
    <p th:text="${msg}"></p>
</div>

<!--
notAuthenticated 标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
-->
<div shiro:notAuthenticated>
    <p th:text="${msg}"></p>
</div>

<!--
notAuthenticated 标签:用户未进行身份验证,即没有调用Subject.login进行登录,包括"记住我"也属于未进行身份验证
-->
<div shiro:notAuthenticated>
    <p th:text="${msg}"></p>
</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值