SpringBoot-10-Shiro

十、Shiro

1、Shiro简介

  • Apache Shiro 是一个Java的安全(权限)框架
  • Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以在JavaEE环境。
  • Shiro可以完成:认证,授权,加密,会话管理,Web集成,缓存等。
  • 下载地址:http://shiro.apache.org/
  • GitHub下载地址:https://github.com/apache/shiro.git
Shiro三大对象
  1. Subject 用户
  2. SecurityManager 安全管理所有用户
  3. Realm 连接数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CBDJ5BYh-1607428903125)(F:\Y2课程\Y2课程体系\SpringBootNote\img\image-20201208134023544.png)]

2、快速搭建

文件路径shiro-master\samples\quickstart

  • 导入依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>
        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
    
  • 配置文件(log4j.properties、shiro.ini)

  • Java类(Quickstart)

默认用的日志是commons-logging

代码分析
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {
    // 使用了日志框架输出
    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {
        // 创建工厂
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

// 1       获取当前的用户对象
        Subject currentUser = SecurityUtils.getSubject();

//        通过当前用户拿到Session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

//  2      测试当前的用户是否被认证
        if (!currentUser.isAuthenticated()) {
//            通过账号的用户名和密码生成一个令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//            设置记住我
            token.setRememberMe(true);
            try {
                currentUser.login(token);   // 执行了登录操作
            } catch (UnknownAccountException uae) { // 用户名不存在
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {  //  密码不对
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {      // 用户名被锁定
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            catch (AuthenticationException ae) {        // 认证异常
                //unexpected condition?  error?
            }
        }

//        获取当前用户的一个认证
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

//  3      测试角色
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

//  4      监测此用户有什么权限(粗粒度)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

//  5      监测此用户有什么权限(细粒度)
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

//  6      注销
        currentUser.logout();
//  7      结束    
        System.exit(0);
    }
}

3、Shiro整合SpringBoot

1、步骤
  1. 创建SpringBoot项目

  2. 导入thymeleaf依赖

    <!--        Thymeleaf模板依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-java8time</artifactId>
    </dependency>
    
  3. 导入整合包依赖

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.1</version>
    </dependency>
    
  4. 自定义Realm

    public class UserRealm extends AuthorizingRealm {
        /**
         * 授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("授权");
    
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("认证");
            return null;
        }
    }
    
  5. 创建ShiroConfig配置类

    @Configuration
    public class ShiroConfig{
        // ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean bean =  new ShiroFilterFactoryBean();
    //        设置安全管理器
            bean.setSecurityManager(defaultWebSecurityManager);
            return bean;
        }
        // DafaultWebSecurityManager
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 关联Realm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
        // 创建realm对象
        @Bean
        public UserRealm userRealm(){
            return new UserRealm();
        }
    }
    
  6. 在Shiro配置类中ShiroFilterFactoryBean中设置过滤器

    1. 设置安全管理器bean.setSecurityManager(defaultWebSecurityManager);

    2. 添加Shiro的内置过滤器

      Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
      filterChainDefinitionMap.put("/user/*","authc");
      bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
      
    3. 设置拦截后的登录页面bean.setLoginUrl("/toLogin");

2、Shiro内置过滤器:

​ 参考源码类DefaultFilter

  1. /admin/**=anon :无参,表示可匿名访问
  2. /admin/user/**=authc :无参,表示需要认证才能访问
  3. /admin/user/**=authcBasic :无参,表示需要httpBasic认证才能访问
  4. /admin/user/**=ssl :无参,表示需要安全的URL请求,协议为https
  5. /home=user :表示用户不一定需要通过认证,只要曾被 Shiro 记住过登录状态就可以正常发起 /home 请求
  6. /edit=authc,perms[admin:edit]:表示用户必需已通过认证,并拥有 admin:edit 权限才可以正常发起 /edit 请求
  7. /admin=authc,roles[admin] :表示用户必需已通过认证,并拥有 admin 角色才可以正常发起 /admin 请求
  8. /admin/user/**=port[8081] :当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString
  9. /admin/user/**=rest[user] :根据请求方式来识别,相当于 /admins/user/**=perms[user:get]或perms[user:post] 等等
  10. /admin**=roles["admin,guest"] :允许多个参数(逗号分隔),此时要全部通过才算通过,相当于hasAllRoles()
  11. /admin**=perms["user:add:*,user:del:*"]:允许多个参数(逗号分隔),此时要全部通过才算通过,相当于isPermitedAll()
3、认证
  1. 在控制器处理前端的登录请求中进行认证

    1. 获取当前用户

      • Subject subject = SecurityUtils.getSubject();
    2. 封装用户的用户名和密码成令牌(用户名通过name和接口传参得到)

      • UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    3. 执行登录方法,subject.login(token);

      并捕捉异常

      try {
      	subject.login(token);       // 执行登录方法,如果没有异常就说明OK了
      	return "index";
      } catch (UnknownAccountException uae) { // 用户名不存在
      	model.addAttribute("massage","用户名不存在");
      	return "login";
      } catch (IncorrectCredentialsException ice) {  //  密码不对
      	model.addAttribute("massage","密码不对");
      	return "login";
      } catch (LockedAccountException lae) {      // 用户名被锁定
      	model.addAttribute("massage","用户名被锁定");
      	return "login";
      }
      catch (AuthenticationException ae) {        // 认证异常
      	model.addAttribute("massage","认证异常");
      	return "login";
      }
      
  2. 每执行一次登录操作(subject.login(token);)都会进行一次认证(doGetAuthenticationInfo

    String name = "root";
    String password = "2019";
    UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
    if (!usernamePasswordToken.getUsername().equals(name)){
       return null;        // 抛出异常UnknownAccountException
    }
    //        密码认证
    return new SimpleAuthenticationInfo("",password,"");
    

4、Shiro整合Mybatis

1、步骤
  1. 导入依赖

    <!--        Mysql驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    <!--        Druid数据源-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.3</version>
            </dependency>
    <!--        Log4j-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.1</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.16</version>
            </dependency>
    
  2. 创建yaml文件配置数据源

    spring:
      datasource:
        username: root
        password: 2019
        url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        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. 建立mybatis的映射

    mybatis.mapper-locations=classpath:mapper/*.xml
    mybatis.type-aliases-package=com.aaron.pojo
    
  4. 创建实体类(pojo)

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Userguang {
        private int id;
        private String name;
        private String pwd;
    }
    
  5. 创建数据访问层(Mapper)

    @Mapper
    @Repository
    public interface UserMapper {
        /**
         * 通过用户名登录
         * @return
         */
        Userguang queryUserByName(String name);
    }
    
  6. 在配置(resources)中创建mapper的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.aaron.mapper.UserMapper">
        <select id="queryUserByName" resultType="com.aaron.pojo.Userguang">
        select * from userguang where name = #{name}
      </select>
    </mapper>
    
  7. 创建业务层(Service)

    public interface UserService {
        /**
         * 通过用户名登录
         * @return
         */
        Userguang queryUserByName(String name);
    }
    
  8. 实现业务层

    @Service
    public class UserServiceimpl implements UserService {
        private UserMapper userMapper;
        @Autowired
        public void setUserMapper(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    
        @Override
        public Userguang queryUserByName(String name) {
            return userMapper.queryUserByName(name);
        }
    }
    
  9. 在通过业务queryUserByName判断用户输入的账户是否存在

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("认证");
            UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
            Userguang userguang = userService.queryUserByName(usernamePasswordToken.getUsername());
            if (userguang.getName() == null){
                return null;        // 抛出异常UnknownAccountException
            }
    //        密码认证 可以 MD5加密   MD5盐值加密
            return new SimpleAuthenticationInfo("",userguang.getPwd(),"");
        }
    

密码加密

​ 所有的加密方式都在CredentialsMatcher接口 下实现的

在这里插入图片描述

默认为SimpleCredentialsMatcher类,其他的方法也都继承此类。

2、授权
  • 定义权限(有此权限才能访问这个请求)

    在配置类ShiroConfiggetShiroFilterFactoryBean方法中设置

    filterChainDefinitionMap.put("/user/add","perms[user:add]");
    filterChainDefinitionMap.put("/user/update","perms[user:update]");
    
  • 给予权限(给当前请求赋予权限)

    UserRealm类中的授权方法AuthorizationInfo设置

    授权方法想拿到当前账户信息,需要让认证方法拿给授权方法

    • 通过返回的SimpleAuthenticationInfo对象(“对象”,,“密码”,“xxx”)

    在授权方法里拿认证给的对象信息

    • 获取当前登录的对象Subject subject = SecurityUtils.getSubject();
    • 拿认证方法给的对象信息Userguang principal = (Userguang)subject.getPrincipal();
    • 设置当前请求的权限info.addStringPermission(principal.getPerms());

这样就实现了真正的业务,数据库表中字段存储权限,通过addStringPermission设置权限,设置的权限就是数据库表中字段的内容。内容就是此用户的权限

5、Shiro整合Thymeleaf

1、步骤
  1. 导入依赖

    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
    
  2. 配置ShiroDialect用来整合Shirothymeleaf

    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
    
  3. 设置标签页头

    <html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    
  4. 判断当前登录的用户的权限hasPermission

    <div shiro:hasPermission="user:add">
        <a th:href="@{/user/add}">add</a>
    </div>
    
2、动态登录与注销

实现思路:判断是否登录,如果未登录显示登录按钮,如果登录显示注销按钮。

**1、登录:**判断用户是否登录需要看Session中是否有用户(在登录成功后,把用户信息放到Session中)

//        登录成功后,把用户的信息方法Session中
//            1、获取当前对象
        Subject currentSubject = SecurityUtils.getSubject();
//            2、获取当前对象的Session
        Session session = currentSubject.getSession();
//            3、设置把登录成功的信息放到Session中
        session.setAttribute("loginUser",userguang);
<div th:if="${session.loginUser == null}">
    <p><a th:href="@{/toLogin}">登录</a></p>
</div>

2、注销

ShiroFilterFactoryBean方法中对注销进行约定

Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/logout","logout");

在控制器中写处理请求

@RequestMapping("/logout")
public void logout(){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
}

在前端判断用户是否已经登录,如果登录发送注销请求。

<div th:if="${session.loginUser != null}">
    <p><a th:href="@{/logout}">注销</a></p>
</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值