用SpringBoot、Redis和Shiro实现权限管理

风华绝代 2024-03-18 ⋅ 20 阅读

引言

在现代的Web应用程序中,权限管理是一个非常重要的功能。它可以确保只有授权用户可以访问特定的资源和操作。SpringBoot是一个无配置的快速开发框架,Redis是一个高性能的键值存储数据库,而Shiro是一个用于身份验证、授权和加密的开源Java安全框架。本文将介绍如何使用SpringBoot、Redis和Shiro结合实现强大的权限管理系统。

1. 创建SpringBoot项目

首先,我们需要创建一个SpringBoot项目。可以使用SpringInitializr或者手动创建一个空的Maven项目。

2. 配置Redis

在SpringBoot项目中,我们可以通过添加Redis相关的依赖来实现与Redis的集成。在pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

接下来,在application.properties文件中配置Redis的连接信息:

spring.redis.host=localhost
spring.redis.port=6379

3. 集成Shiro

Shiro提供了一套完整的身份验证、授权和加密解决方案。我们可以通过添加Shiro相关的依赖来集成Shiro。在pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.8.0</version>
</dependency>

接下来,我们需要创建一个ShiroConfig类来配置Shiro:

@Configuration
public class ShiroConfig {

    @Autowired
    private RedisCacheManager redisCacheManager;

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 设置静态资源不需要权限验证
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");

        // 设置登录接口不需要权限验证
        filterChainDefinitionMap.put("/login", "anon");

        // 设置其他接口需要权限验证
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisConnectionFactory(redisConnectionFactory);
        return redisCacheManager;
    }

    @Bean
    public SecurityManager securityManager(Realm realm, SessionsSecurityManager sessionsSecurityManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(redisCacheManager);
        securityManager.setSessionManager(sessionsSecurityManager);
        return securityManager;
    }

    @Bean
    public SessionsSecurityManager sessionsSecurityManager(RedisCacheManager redisCacheManager) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setCacheManager(redisCacheManager);
        sessionManager.setGlobalSessionTimeout(1800000); // 30 minutes
        sessionManager.setDeleteInvalidSessions(true);
        return sessionManager;
    }

    @Bean
    public Realm realm(CredentialsMatcher credentialsMatcher) {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(credentialsMatcher);
        return userRealm;
    }

    @Bean
    public CredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("md5");
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return credentialsMatcher;
    }
}

在上面的配置中,我们通过redisCacheManager配置了Redis作为缓存,设置了登录接口和静态资源不需要权限验证,其他接口需要权限验证。我们同时配置了sessionManager来管理用户的会话信息。

接下来,我们需要创建一个自定义的Realm类来提供用户信息和权限信息。这里我创建一个UserRealm类继承AuthorizingRealm,并实现其中的方法:

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String username = (String) getAvailablePrincipal(principals);
        
        User user = userService.getUserByUsername(username);
        if (user != null) {
            Set<String> roles = userService.getUserRoles(user.getId());
            authorizationInfo.setRoles(roles);
            
            Set<String> permissions = userService.getUserPermissions(user.getId());
            authorizationInfo.setStringPermissions(permissions);
        }
        
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        User user = userService.getUserByUsername(username);
        
        if (user == null) {
            throw new UnknownAccountException();
        }
        
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
        return authenticationInfo;
    }
}

在上面的代码中,doGetAuthorizationInfo方法用于获取用户的角色和权限信息,doGetAuthenticationInfo方法用于验证用户的身份信息。

4. 实现登录和鉴权接口

我们需要创建一些接口来实现用户的登录和鉴权。

首先,我们需要创建一个LoginController类来处理登录逻辑:

@RestController
public class LoginController {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    @PostMapping("/login")
    public String login(String username, String password) {
        User user = userService.getUserByUsername(username);

        if (user == null || !user.getPassword().equals(password)) {
            return "登录失败";
        }

        // 将用户信息存储到Redis中
        redisTemplate.boundValueOps(username).set(user);

        return "登录成功";
    }

    @GetMapping("/unauthorized")
    public String unauthorized() {
        return "未经授权";
    }
}

上面的代码中,我们使用RedisTemplate将用户信息存储到Redis中,以便于后续的鉴权操作。

接下来,我们需要为一些需要鉴权的接口添加@RequiresAuthentication@RequiresRoles@RequiresPermissions注解来限制访问:

@RestController
public class HelloController {

    @GetMapping("/hello")
    @RequiresAuthentication
    @RequiresRoles("admin")
    public String hello() {
        return "Hello, World!";
    }
}

在上面的代码中,我们使用了@RequiresAuthentication注解要求用户进行身份验证,@RequiresRoles注解要求用户具有指定的角色,@RequiresPermissions注解要求用户具有指定的权限。

5. 测试

现在,我们可以启动SpringBoot应用程序,并使用浏览器或者API测试工具来测试接口。首先访问/login接口进行登录,然后访问/hello接口进行鉴权请求。如果用户登录成功且具有相应的角色和权限,将会返回"Hello, World!",否则将会返回"未经授权"。

总结

在本文中,我们介绍了如何使用SpringBoot、Redis和Shiro实现权限管理。通过集成Shiro,我们可以方便地进行用户身份验证和授权操作。同时,通过使用Redis作为缓存,可以提高系统的性能和并发能力。希望本文对您有所帮助,并能帮助您构建一个安全可靠的权限管理系统。


全部评论: 0

    我有话说: