SpringBoot整合Shiro和Redis的示例代码

下面我将为你详细讲解“SpringBoot整合Shiro和Redis的示例代码”的具体过程,包含示例代码说明。

下面我将为你详细讲解“SpringBoot整合Shiro和Redis的示例代码”的具体过程,包含示例代码说明。

一、引入相关依赖

首先需要在 pom.xml 文件中引入相关依赖,包括 SpringBoot、Shiro 和 Redis 的依赖,示例代码如下:

<dependencies>
    <!-- SpringBoot 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
    </dependency>

    <!-- Shiro 依赖 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>1.6.0</version>
    </dependency>

    <!-- Redis 客户端依赖 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
        <version>2.8.0</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
</dependencies>

二、配置 Shiro

application.yml(或 application.properties) 文件中配置 Shiro 的相关信息,示例代码如下:

shiro:
  hashAlgorithmName: md5 # 修改 hash 算法,默认是 sha-256
  hashIterations: 2 # 修改 hash 迭代次数
  redis:
    host: localhost
    port: 6379
    timeout: 10000
    password: XXXXXX
    database: 0

其中,hashAlgorithmNamehashIterations 是用来加密密码的,这里以 md5 和 2 为例。

三、编写 Shiro 自定义 Realm

需要自定义一个 Realm 来将 Shiro 和 Redis 集成起来。示例代码如下:

public class CustomRealm extends AuthorizingRealm {

    private RedisManager redisManager;

    public void setRedisManager(RedisManager redisManager) {
        this.redisManager = redisManager;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(redisManager.getRoles(username));
        authorizationInfo.setStringPermissions(redisManager.getPermissions(username));
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
        String dbPassword = redisManager.getPassword(username);
        if (dbPassword == null || !dbPassword.equals(password)) {
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

四、编写 RedisManager

RedisManager 是用来访问 Redis 缓存的,示例代码如下:

public class RedisManager {

    private JedisPool jedisPool;

    public RedisManager(String host, int port, int timeout, String password, int database) {
        jedisPool = new JedisPool(new JedisPoolConfig(), host, port, timeout, password, database);
    }

    public Jedis getJedis() {
        return jedisPool.getResource();
    }

    public String getPassword(String username) {
        try (Jedis jedis = getJedis()) {
            return jedis.hget("user:" + username, "password");
        }
    }

    public Set<String> getRoles(String username) {
        try (Jedis jedis = getJedis()) {
            return jedis.smembers("user:" + username + ":roles");
        }
    }

    public Set<String> getPermissions(String username) {
        try (Jedis jedis = getJedis()) {
            return jedis.smembers("user:" + username + ":permissions");
        }
    }
}

五、集成 Shiro 和 Redis

这里使用 Shiro 和 Redis 的集成插件 ShiroRedisCacheManager

@Configuration
public class ShiroConfig {

    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCachingEnabled(true);
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        customRealm.setAuthenticationCachingEnabled(true);
        customRealm.setAuthenticationCacheName("authenticationCache");
        customRealm.setAuthorizationCachingEnabled(true);
        customRealm.setAuthorizationCacheName("authorizationCache");
        customRealm.setRedisManager(redisManager());
        return customRealm;
    }

    @Bean
    public RedisManager redisManager() {
        return new RedisManager(
                environment.getProperty("shiro.redis.host"),
                Integer.valueOf(environment.getProperty("shiro.redis.port")),
                Integer.valueOf(environment.getProperty("shiro.redis.timeout")),
                environment.getProperty("shiro.redis.password"),
                Integer.valueOf(environment.getProperty("shiro.redis.database"))
        );
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName(environment.getProperty("shiro.hashAlgorithmName"));
        matcher.setHashIterations(Integer.valueOf(environment.getProperty("shiro.hashIterations")));
        return matcher;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");

        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new ShiroRedisCacheManager(redisManager());
        redisCacheManager.setKeyPrefix("shiro:cache:");
        redisCacheManager.setExpire(1800); // 设置缓存过期时间,单位秒,默认1小时
        return redisCacheManager;
    }

}

六、示例说明

示例1:验证用户登录

首先创建一个账号密码为 test/test123 的用户,在该账户下分配两个角色 adminuser,每个角色各有两个权限 user:adduser:delete

在前端页面进行登录操作,后台服务器验证用户名和密码。如果验证成功,CustomRealm#doGetAuthorizationInfo() 方法将被调用,用户的角色和权限将存储在 Redis 中。

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login(@RequestParam("username") String username,
                        @RequestParam("password") String password,
                        Model model) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return "redirect:/index";
        } catch (Exception e) {
            model.addAttribute("errorMsg", "用户名或密码错误!");
            return "login";
        }
    }

}

示例2:查询用户权限

在前端页面进行某个操作时会访问后端服务,后端服务需要验证该用户是否有权限进行该操作。由于用户权限已经存储在 Redis 中,直接从 Redis 中获取即可。

@RequestMapping("/operation")
@ResponseBody
public String operation() {
    Subject subject = SecurityUtils.getSubject();
    if (subject == null || !subject.isAuthenticated()) {
        return "用户未登录或登录状态已过期!";
    }
    if (subject.isPermitted("user:add")) {
        return "用户具有添加权限!";
    } else {
        return "用户没有添加权限!";
    }
}

以上便是 SpringBoot 整合 ShiroRedis 的完整攻略。

本文标题为:SpringBoot整合Shiro和Redis的示例代码

基础教程推荐