Shiro

会话管理

        Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。

会话相关API

  • Subject.getSession(): 获取当前Subject的会话。如果当前没有会话,根据参数的不同会有不同的行为:
    • Subject.getSession(true): 如果当前没有会话,则创建一个新的会话。
    • Subject.getSession(false): 如果当前没有会话,则返回null。
  • session.setAttribute(Object key, Object value): 设置会话属性。
  • session.getAttribute(Object key): 根据键获取会话属性。
  • session.removeAttribute(Object key): 根据键删除会话属性。

SessionDAO

  • AbstractSessionDAO类:这是SessionDAO的一个基本实现类,提供了生成会话ID等基础功能。
  • CachingSessionDAO类:这是一个提供了缓存功能的实现类,它位于应用层与持久化层之间,用于减少频繁请求持久化层的开销。它重写了AbstractSessionDAO中的部分方法,并实现了SessionDAO中的其他方法。不过,CachingSessionDAO中的doUpdate和doDelete方法是预留给子类去实现的。
  • MemorySessionDAO类:这是一个将Session保存在内存中的实现类,其存储结构是ConcurrentHashMap。不过,在实际项目中,这个实现类用得并不多。
  • EnterpriseCacheSessionDAO类:这是一个提供了缓存功能的Session维护实现类。它设置了默认的缓存管理器(AbstractCacheManager)和默认的缓存实例(MapCache),并实现了缓存效果。不过,它并没有实现持久化操作,只是简单地提供了缓存实现。开发者可以继承这个类,并重写doCreate、doReadSession、doUpdate和doDelete等方法来实现持久化操作。

缓存

具体实现

1. 添加依赖

        <!-- Shiro配置Redis缓存管理 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.3.1</version>
        </dependency>

2. application.properties或 application.yml配置文件中添加Redis配置

spring:
  #Redis配置
  data:
    redis:
      # Redis 服务器的主机名或 IP 地址
      host: localhost
      # Redis 服务器的端口号,默认是 6379
      port: 6379
      # Redis 服务器密码
      password: luoqiangwu
      # Redis 的 lettuce 客户端连接池配置
      lettuce:
        pool:
          # 连接池最大活动连接数,默认为 8
          max-active: 8
          # 连接池最大阻塞等待时间,-1 表示无限等待
          max-wait: -1
          # 连接池中的最大空闲连接数,默认为 8
          max-idle: 8
          # 连接池中最小空闲连接数,默认为 0
          min-idle: 0
      timeout: 5000

3. ShiroConfig

@Configuration
public class ShiroConfig {

    // 注入Redis参数,从application.yml获得
    @Value("${spring.data.redis.host}")
    private String host;
    @Value("${spring.data.redis.port}")
    private int port;
    @Value("${spring.data.redis.password}")
    private String password;
    @Value("${spring.data.redis.timeout}")
    private int timeout;

    @Resource
    private RoleService roleService;

    /**
     * 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),
     * 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个 bean(DefaultAdvisorAutoProxyCreator 和 AuthorizationAttributeSourceAdvisor)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

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

    /**
     * 验证匹配规则
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用 md5 算法进行加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置散列次数:意为加密几次
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定义Realm(基于数据库)
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm shiroRealm = new MyShiroRealm();
        // 设置启用缓存,并设置缓存名称
        shiroRealm.setCachingEnabled(true);
        shiroRealm.setAuthorizationCachingEnabled(true);
        shiroRealm.setAuthorizationCacheName("authorization");
        // 设置凭证(密码)匹配器
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return shiroRealm;
    }

    /**
     * Thymeleaf页面上使用shiro标签
     */
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * Redis管理器
     */
    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host + ":" + port); // #shiro-redis v3.3.1
        redisManager.setPassword(password);
        redisManager.setTimeout(timeout);
        return redisManager;
    }

    /**
     * 缓存管理器
     */
    @Bean
    public CacheManager shiroCacheManager() {
        RedisCacheManager cacheManager = new RedisCacheManager();
        cacheManager.setRedisManager(redisManager());
        // 缓存名称
        cacheManager.setPrincipalIdFieldName("usrName");
        // 缓存有效时间
        cacheManager.setExpire(1800);
        return cacheManager;
    }

    /**
     * 会话持久化操作
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * 安全管理器SecurityManager
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 注入自定义的Realm
        securityManager.setRealm(myShiroRealm());
        // 注入缓存管理器
        securityManager.setCacheManager(shiroCacheManager());
        // 注入会话管理器
        securityManager.setSessionManager(sessionManager());
        // Shiro2.0.1新版本需要SecurityUtils设置SecurityManager,否则报错(org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code)
        SecurityUtils.setSecurityManager(securityManager);
        return securityManager;
    }

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

    /**
     * Shiro过滤器:权限验证
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
        // 注入SecurityManager
        shiroFilterFactory.setSecurityManager(securityManager);
        // 权限验证:使用 Filter 控制资源(URL)的访问
        shiroFilterFactory.setLoginUrl("/login"); // 登录页面URL
        shiroFilterFactory.setSuccessUrl("/main"); // 登录成功URL
        shiroFilterFactory.setUnauthorizedUrl("/403"); // 没有权限跳转403页面

        // 权限配置集合,必须使用LinkedHashMap(有序集合)
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 静态资源授权
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/fonts/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/localcss/**", "anon");
        filterChainDefinitionMap.put("/localjs/**", "anon");

        // API接口请求授权
        filterChainDefinitionMap.put("/api/**", "anon");

        // 登录退出页面设置
        filterChainDefinitionMap.put("/login", "anon"); // 点击登录按钮时放行
        filterChainDefinitionMap.put("/logout", "logout"); // 点击退出按钮时放行

        // 配置需要特定权限才能访问的资源(URL)
        // 静态授权:包括全部需要特定权限才能访问的资源(URL)
        /*
        filterChainDefinitionMap.put("/user/list", "perms[用户列表]");
        filterChainDefinitionMap.put("/user/add", "perms[用户添加]");
        filterChainDefinitionMap.put("/user/edit", "perms[用户编辑]");
        filterChainDefinitionMap.put("/user/del", "perms[用户删除]");
        */

        // 动态授权
        List<Right> rights = roleService.findAllRights();
        for (Right right : rights) {
            if (right.getRightUrl() != null && !right.getRightUrl().trim().equals("")) {
                filterChainDefinitionMap.put(right.getRightUrl(), "perms[" + right.getRightCode() + "]");
            }
        }

        // 配置认证访问:其他资源(URL)必须认证通过才能访问
        filterChainDefinitionMap.put("/**", "authc"); // 必须放在过滤器链的最后面

        shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactory;
    }
}

加密

哈希与盐

        如果你需要保存密码,你要考虑如何保护这些密码数据,像下面那样直接将密码写入数据库中是极不安全的,因为任何可以打开数据库的人,都将可以直接看到这些密码。

        解决的办法是将密码加密后再存储进数据库,比较使用的加密方法是使用哈希函数(散列算法),常见的散列算法如MD5、SHA等。哈希函数的具体定义,大家可以在网上或者相关书籍中查询到,简单地说,它的特性如下:

  1. 原始密码经哈希函数计算后得到一个哈希值
  2. 改变原始密码,哈希函数计算出的哈希值也会相应改变
  3. 同样的密码,哈希值也是相同的

哈希函数是单向、不可逆的。也就是说从哈希值,你无法推算出原始的密码是多少。

加密与验证

        Shiro提供了PasswordService及CredentialsMatcher用于提供加密密码及验证密码服务。

public interface PasswordService {
    String encryptPassword(Object var1) throws IllegalArgumentException;

    boolean passwordsMatch(Object var1, String var2);
}

 具体实现

MyShiroRealm

public class MyShiroRealm extends AuthorizingRealm {
    @Lazy // Shiro框架执行比@Cacheable注解AOP代理早,导致对象代理不成功
    @Resource
    private UserService userService;

    @Lazy // Shiro框架执行比@Cacheable注解AOP代理早,导致对象代理不成功
    @Resource
    private RoleService roleService;

    /**
     * 自定义认证流程(机制)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("调用 MyShiroRealm.doGetAuthenticationInfo 获取身份信息!");
        // 获得身份信息
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String userName = token.getUsername();

        // 从数据库中根据用户名查询用户对象(联表查询,包含角色信息)
        User user = userService.findUserByUsrName(userName);
        if (user == null) {
            throw new UnknownAccountException("用户不存在"); // 用户不存在
        }
        if (user.getUsrFlag() == null || user.getUsrFlag() == 0) {
            throw new LockedAccountException("账号已锁定");
        }
        // 通过用户获取角色信息
        Role role = user.getRole(); // int &207 -> list
        // 根据角色获取权限列表
        Set<Right> rights = roleService.findRightByRoleId(role.getRoleId());
        // 给角色设置权限集合
        role.setRights(rights);
        System.out.println(user);
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getUsrPassword(), ByteSource.Util.bytes("czkt"), this.getName());
        // 返回身份信息
        return info;
    }

    /**
     * 自定义授权(认证通过才会授权)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("调用 MyShiroRealm.doGetAuthorizationInfo 获取权限信息!");
        // 获得权限信息
        User user = (User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 静态授权:授予主体(用户)相应的角色和权限
        /*
        info.addRole(user.getRole().getRoleName());
        info.addStringPermission("用户列表"); // 所有用户拥有"用户列表"权限
        if ("管理员".equals(user.getRole().getRoleName())) { // 管理员拥有"增删改"权限
            info.addStringPermission("用户添加");
            info.addStringPermission("用户编辑");
            info.addStringPermission("用户删除");
        }
        */
        // 动态授权:从数据库中获取角色和权限
        Role role = user.getRole();
        if (role != null) {
            info.addRole(role.getRoleName()); // 动态设置角色
            Set<Right> rights = role.getRights();
            if (rights != null && !rights.isEmpty()) {
                for (Right right : rights) {
                    info.addStringPermission(right.getRightCode()); // 动态设置权限
                }
            }
        }
        // 返回授权信息
        return info;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值