Shiro 学习总结

目录

一、Shiro入门与基本配置

1. Shiro概述

核心概念

2. Shiro依赖

3. 设置用户的角色和权限

(1)方式一:在resource中新建shiro-test-1.ini文件(注意后缀:ini)

(2)方式二:使用realm自定义的方式,通过代码从数据库中获取认证的用户数据        User user = (User)principalCollection.getPrimaryPrincipal();

二、Shiro入门使用

1. 基于ini的用户认证

2. 基于ini用户授予

3. 自定义realm

总结:

三、Shiro整合SpringBoot单体架构示例:

1. 引入依赖

2. UserController:进行登录验证

3. realm.CustomRealm

4. config.ShiroConfiguration 

(1)第一种是在ShiroConfiguration中使用filterMap.put(过滤器的方式配置目标地址的请求权限 )

 (2)第二种是在controller中使用注解的方式

5. 异常进行统一处理(handler.BaseExceptionHandler )

四、Shiro会话管理

1. 自定义会话管理

(1)引入依赖

(2)在application.yml中配置redis信息

(3)session.CustomSessionManager

2. 配置会话管理ShiroConfiguration

3. 在controller中进行登录校验


一、Shiro入门与基本配置

1. Shiro概述

Apache Shiro 是一个开源的安全框架,它提供了身份验证、授权、密码学和会话管理等功能。Shiro 的设计目标是简单易用,同时又不失灵活性和强大。它适用于各种类型的Java应用程序,包括传统的Java EE应用程序和基于Spring的应用程序,以及Spring Boot应用程序。

核心概念

Shiro 的核心概念包括:

  • Subject:Shiro中的主体概念,代表当前进行认证或授权的用户。Subject 可以与用户一一对应,也可以一对多。
  • SecurityManager:Shiro的核心接口,负责协调Shiro组件的工作。它管理所有Subject,并负责处理认证、授权、会话管理等。
  • Realm:Shiro与数据源(例如数据库、LDAP或内存)交互的接口,负责获取和验证用户的认证信息和权限信息。
  • Authentication:身份验证过程,用于确定Subject是否为“谁”。
  • Authorization:授权过程,确定Subject是否有权限做“什么”。

2. Shiro依赖

记得导入Shiro的依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>

3. 设置用户的角色和权限

(1)方式一:在resource中新建shiro-test-1.ini文件(注意后缀:ini)

用于模拟从数据库中查询用户

数据的格式:用户名=密码

[users]
zhangsan=123456
lisi=1234

给每个用户赋予角色,以及每个角色的权限

数据的格式:用户名=密码,角色名

角色=权限列表

[users]
zhangsan=123456,role1,role2
lisi=1234,role2

[roles]
role1=user:save,user:update
role2=user:find

(2)方式二:使用realm自定义的方式,通过代码从数据库中获取认证的用户数据
        User user = (User)principalCollection.getPrimaryPrincipal();

二、Shiro入门使用

1. 基于ini的用户认证

这里的IniSecurityManagerFactory("classpath:shiro-test-1.ini")中的shiro-test-1.ini对应source中的shiro-test-1.ini

@Test
    void testLogin() {
        // 创建SecurityManager工厂,用于生成SecurityManager实例
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-1.ini");

        // 通过工厂获取SecurityManager实例
        SecurityManager securityManager = factory.getInstance();

        // 设置当前线程的SecurityManager
        SecurityUtils.setSecurityManager(securityManager);

        // 获取Subject实例,Subject代表了用户的安全认证状态
        Subject subject = SecurityUtils.getSubject();

        // 定义用户名和密码
        String username = "zhangsan";
        String password = "123456";

        // 创建UsernamePasswordToken,用于封装用户名和密码
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        // 执行登录方法,会触发Realm.doGetAuthenticationInfo的执行
        subject.login(token);

        // 打印用户是否认证
        System.out.println(subject.isAuthenticated());

        // 打印认证后的主体信息,通常为用户名
        System.out.println(subject.getPrincipal());
    }

2. 基于ini用户授予

@Test
    void testLogin() {
        // 创建SecurityManager工厂,用于生成SecurityManager实例,这里使用了Ini配置文件
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-1.ini");

        // 通过工厂获取SecurityManager实例
        SecurityManager securityManager = factory.getInstance();

        // 设置当前线程的SecurityManager
        SecurityUtils.setSecurityManager(securityManager);

        // 获取Subject实例,Subject代表了用户的安全认证状态
        Subject subject = SecurityUtils.getSubject();

        // 定义用户名和密码
        String username = "zhangsan";
        String password = "123456";

        // 创建UsernamePasswordToken,用于封装用户名和密码
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        // 执行登录方法,会触发Realm.doGetAuthenticationInfo的执行
        subject.login(token);

        // 检查用户是否拥有角色"role1"
        System.out.println(subject.hasRole("role1"));

        // 检查用户是否拥有权限"user:save"
        System.out.println(subject.isPermitted("user:save"));
    }

3. 自定义realm

在source中创建shiro-test-2.ini

[main]
#声明realm,路径记得修改
permRealm=com.example.shirodemo.shiro.PermissionRealm
#注册realm到securityManager中
securityManager.realm=$permRealm

realm.PermissionRealm文件信息:

public class PermissionRealm extends AuthorizingRealm {
    // 设置当前realm的名称
    public void setName(String name) {
        super.setName("permissionRealm");
    }

    // 授权方法,用于定义用户的角色和权限
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 从PrincipalCollection中获取登录的用户名
        String username = (String) principalCollection.getPrimaryPrincipal();

        // 模拟用户的权限和角色信息,实际应用中这些数据通常来自于数据库
        List<String> perms = new ArrayList<>(); // 用户权限列表
        perms.add("user:save"); // 添加权限
        perms.add("user:update"); // 添加权限
        List<String> roles = new ArrayList<>(); // 用户角色列表
        roles.add("role1"); // 添加角色
        roles.add("role2"); // 添加角色

        // 创建SimpleAuthorizationInfo实例,用于封装授权信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 为用户添加权限
        info.addStringPermissions(perms);
        // 为用户添加角色
        info.addRoles(roles);

        // 返回授权信息
        return info;
    }

    // 认证方法,用于验证用户的身份
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 转换AuthenticationToken为UsernamePasswordToken,用于获取用户名和密码
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String username = upToken.getUsername(); // 获取用户名
        String password = new String(upToken.getPassword()); // 获取密码

        // 简单的认证逻辑,实际应用中通常需要查询数据库验证用户名和密码
        if ("123456".equals(password)) {
            // 创建SimpleAuthenticationInfo实例,用于封装认证信息
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
            // 返回认证信息
            return info;
        }

        // 如果认证失败,抛出异常
        throw new RuntimeException("用户名或密码错误");
    }
}

总结:

ini是通过在文件中表名用户和角色的信息;

而realm自定义的方式,是通过代码的方式表名用户和角色的信息

三、Shiro整合SpringBoot单体架构示例:

1. 引入依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
 <dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-core</artifactId>
     <version>1.3.2</version>
</dependency>

2. UserController:进行登录验证

@PostMapping("/login")
    public String login(String username,String password) {
        try{
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken uptoken = new 
UsernamePasswordToken(username,password);
            subject.login(uptoken);
            return "登录成功";
       }catch (Exception e) {
            return "用户名或密码错误";
       }
   }

3. realm.CustomRealm

public class CustomRealm extends AuthorizingRealm {
 
    @Override
    public void setName(String name) {
        super.setName("customRealm");
   }
 
    @Autowired
    private UserService userService;
 
    /**
     * 构造授权方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection 
principalCollection) {
//1.获取认证的用户数据
        User user = (User)principalCollection.getPrimaryPrincipal();
        //2.构造认证数据
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<Role> roles = user.getRoles();
        for (Role role : roles) {
            //添加角色信息
            info.addRole(role.getName());
            for (Permission permission:role.getPermissions()) {
                //添加权限信息
                info.addStringPermission(permission.getCode());
           }
       }
        return info;
   }
 
    /**
     * 认证方法
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken 
authenticationToken) throws AuthenticationException {
        //1.获取登录的upToken
        UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
        //2.获取输入的用户名密码
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());
        //3.数据库查询用户
        User user = userService.findByName(username);
        //4.用户存在并且密码匹配存储用户数据
        if(user != null && user.getPassword().equals(password)) {
            return new 
SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
       }else {
            //返回null会抛出异常,表明用户不存在或密码不匹配
            return null;
       }
   }
}

4. config.ShiroConfiguration 

@Configuration
public class ShiroConfiguration {
 
    //配置自定义的Realm
    @Bean
    public CustomRealm getRealm() {
        return new CustomRealm();
   }

//配置安全管理器
    @Bean
    public SecurityManager securityManager(CustomRealm realm) {
        //使用默认的安全管理器
        DefaultWebSecurityManager securityManager = new 
DefaultWebSecurityManager(realm);
        //将自定义的realm交给安全管理器统一调度管理
        securityManager.setRealm(realm);
        return securityManager;
   }
 
    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        //1.创建shiro过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(securityManager);
        //3.通用配置(配置登录页面,登录成功页面,验证未成功页面)
        filterFactory.setLoginUrl("/autherror?code=1"); //设置登录页面
        filterFactory.setUnauthorizedUrl("/autherror?code=2"); //授权失败跳转页面
        //4.配置过滤器集合
        /**
         * key :访问连接
         *     支持通配符的形式
         * value:过滤器类型
         *     shiro常用过滤器
         *         anno   :匿名访问(表明此链接所有人可以访问)
         *         authc   :认证后访问(表明此链接需登录认证成功之后可以访问)
         */
        Map<String,String> filterMap = new LinkedHashMap<String,String>();
        // 配置不会被拦截的链接 顺序判断
        filterMap.put("/user/home", "anon");
        filterMap.put("/user/**", "authc");
 
        //5.设置过滤器
        filterFactory.setFilterChainDefinitionMap(filterMap);
        return filterFactory;
   }
 
    //配置shiro注解支持
    @Bean
    public AuthorizationAttributeSourceAdvisor 
authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new 
AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
   }
}

有两种授权的方式:

(1)第一种是在ShiroConfiguration中使用filterMap.put(过滤器的方式配置目标地址的请求权限 )

示例:

//匿名访问(所有人员可以使用)
        filterMap.put("/user/home", "anon");
        //具有指定权限访问
        filterMap.put("/user/find", "perms[user-find]");
        //认证之后访问(登录之后可以访问)
        filterMap.put("/user/**", "authc");
        //具有指定角色可以访问
        filterMap.put("/user/**", "roles[系统管理员]");

 (2)第二种是在controller中使用注解的方式

@RequiresPermissions        //配置到方法上,表明执行此方法必须具有指定的权限

@RequiresRoles         //配置到方法上,表明执行此方法必须具有指定的角色

可以同时使用,也可以使用其中一个注解,示例:

@RequiresRoles(value = "系统管理员")
@RequiresPermissions(value = "user-find")
    public String find() {
        return "查询用户成功";
   }

5. 异常进行统一处理(handler.BaseExceptionHandler )

@ControllerAdvice
public class BaseExceptionHandler {

    @ExceptionHandler(value = AuthorizationException.class)
    @ResponseBody
    public String error(HttpServletRequest request, HttpServletResponse response,AuthorizationException e) {
		return "未授权";
    }
}

四、Shiro会话管理

1. 自定义会话管理

(1)引入依赖

        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.1.0</version>
        </dependency>

(2)在application.yml中配置redis信息

redis:
   host: 127.0.0.1
   port: 6379

(3)session.CustomSessionManager

package com.example.shirodemo.session;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;

public class CustomSessionManager extends DefaultWebSessionManager {


    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String id = httpServletRequest.getHeader("Authorization");

        if (StringUtils.isEmpty(id)) {
            // 如果找不到会话ID,则调用父类的 getSessionId 方法


            return super.getSessionId(request, response);
        } else {
            // 设置会话ID来源为头部信息
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);

            // 返回会话ID
            return id;
        }
    }
}

2. 配置会话管理ShiroConfiguration

session.ShiroConfiguration:

@Configuration
public class ShiroConfiguration {

    //1.创建realm
    @Bean
    public CustomRealm getRealm() {
        return new CustomRealm();
    }

    //2.创建安全管理器
    @Bean
    public SecurityManager getSecurityManager(CustomRealm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);

        //将自定义的会话管理器注册到安全管理器中
        securityManager.setSessionManager(sessionManager());
        //将自定义的redis缓存管理器注册到安全管理器中
        securityManager.setCacheManager(cacheManager());

        return securityManager;
    }

    //3.配置shiro的过滤器工厂

    /**
     * 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
     *
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        //1.创建过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(securityManager);
        //3.通用配置(跳转登录页面,为授权跳转的页面)
        filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
        filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
        //4.设置过滤器集合

        /**
         * 设置所有的过滤器:有顺序map
         *     key = 拦截的url地址
         *     value = 过滤器类型
         *
         */
        Map<String,String> filterMap = new LinkedHashMap<>();
        //filterMap.put("/user/home","anon");//当前请求地址可以匿名访问

        //具有某中权限才能访问
        //使用过滤器的形式配置请求地址的依赖权限
        //filterMap.put("/user/home","perms[user-home]"); //不具备指定的权限,跳转到setUnauthorizedUrl地址

        //使用过滤器的形式配置请求地址的依赖角色
        //filterMap.put("/user/home","roles[系统管理员]");

        filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问

        filterFactory.setFilterChainDefinitionMap(filterMap);

        return filterFactory;
    }


    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;

    /**
     * 1.redis的控制器,操作redis
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        return redisManager;
    }

    /**
     * 2.sessionDao
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * 3.会话管理器
     */
    public DefaultWebSessionManager sessionManager() {
        CustomSessionManager sessionManager = new CustomSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * 4.缓存管理器
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }




    //开启对shior注解的支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

3. 在controller中进行登录校验

	@PostMapping("/login")
    public String login(String username,String password) {
	    //构造登录令牌
        try {

            /**
             * 密码加密:
             *     shiro提供的md5加密
             *     Md5Hash:
             *      参数一:加密的内容
             *              111111   --- abcd
             *      参数二:盐(加密的混淆字符串)(用户登录的用户名)
             *              111111+混淆字符串
             *      参数三:加密次数
             *
             */
            password = new Md5Hash(password,username,3).toString();

            UsernamePasswordToken upToken = new UsernamePasswordToken(username,password);
            //1.获取subject
            Subject subject = SecurityUtils.getSubject();

            //获取session
            String sid = (String) subject.getSession().getId();

            //2.调用subject进行登录
            subject.login(upToken);
            return "登录成功";
        }catch (Exception e) {
            return "用户名或密码错误";
        }
    }

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值