SpringBoot整合Shiro的代码详解

接下来我会详细讲解“SpringBoot整合Shiro的代码详解”的完整攻略。整个过程分为以下几个步骤:

接下来我会详细讲解“SpringBoot整合Shiro的代码详解”的完整攻略。整个过程分为以下几个步骤:

  1. 添加依赖
  2. 配置Shiro
  3. 编写身份认证和授权逻辑
  4. 添加Web接口
  5. 测试

下面我会一一解释每个步骤的具体内容。

1. 添加依赖

首先需要在pom.xml文件中添加Shiro和SpringBoot的依赖:

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

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

2. 配置Shiro

接下来需要在SpringBoot的配置文件application.properties中添加Shiro的相关配置:

# 设置Shiro的缓存为Redis
shiro.redis.enabled=true
# Redis主机地址
shiro.redis.host=localhost
# Redis主机端口
shiro.redis.port=6379
# Redis密码
shiro.redis.password=
# session过期时间,单位为毫秒
shiro.redis.session-expire=1800000

然后在SpringBoot的启动类Application.java中添加Shiro的配置:

@Configuration
public class ShiroConfig {

    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(env.getProperty("shiro.redis.host"));
        redisManager.setPort(Integer.parseInt(env.getProperty("shiro.redis.port")));
        redisManager.setPassword(env.getProperty("shiro.redis.password"));
        redisManager.setExpire(Integer.parseInt(env.getProperty("shiro.redis.session-expire")));
        return redisManager;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisManager redisManager) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        return redisCacheManager;
    }

    @Bean
    public SessionDAO sessionDAO(RedisManager redisManager) {
        EnterpriseCacheSessionDAO sessionDAO = new EnterpriseCacheSessionDAO();
        sessionDAO.setCacheManager(cacheManager(redisManager));
        return sessionDAO;
    }

    @Bean
    public DefaultWebSecurityManager securityManager(UserRealm userRealm, SessionDAO sessionDAO) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setSessionManager(defaultWebSessionManager(sessionDAO));
        return securityManager;
    }

    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(SessionDAO sessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(sessionDAO);
        return sessionManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilter;
    }
}

其中,RedisManagerRedisCacheManagerSessionDAODefaultWebSecurityManagerDefaultWebSessionManagerShiroFilterFactoryBean都是Shiro提供的Bean,具体作用可以参考Shiro的官方文档。

3. 编写身份认证和授权逻辑

接下来需要编写身份认证和授权逻辑。可以通过继承Shiro提供的AuthorizingRealmAuthenticationRealm类来实现。

public class UserRealm extends AuthorizingRealm {

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        authorizationInfo.setRoles(user.getRoles());
        authorizationInfo.setStringPermissions(user.getPermissions());
        return authorizationInfo;
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        String username = userToken.getUsername();
        String password = new String(userToken.getPassword());

        // 查询数据库中是否有该用户
        User user = userDao.getUserByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("用户名或密码错误");
        }
        if (!password.equals(user.getPassword())) {
            throw new IncorrectCredentialsException("用户名或密码错误");
        }

        // 认证成功,返回身份信息
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, password, getName());
        return authenticationInfo;
    }
}

上面的代码中,doGetAuthorizationInfo方法用于授权,doGetAuthenticationInfo方法用于身份认证。其中,User对象代表当前用户的身份信息,在Shiro中被称为“Principal”。

4. 添加Web接口

接下来需要添加Web接口,提供访问控制的功能。可以通过SpringBoot的@Controller@RequestMapping注解来实现。

@Controller
public class UserController {

    @RequestMapping("/login")
    public String login(String username, String password) {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            return "login";
        } catch (IncorrectCredentialsException e) {
            return "login";
        } catch (AuthenticationException e) {
            return "login";
        }
    }

    @RequestMapping("/unauthorized")
    public String unauthorized() {
        return "unauthorized";
    }

    @RequestMapping("/logout")
    public String logout() {
        Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            subject.logout();
        }
        return "login";
    }

    @RequestMapping("/")
    @RequiresPermissions("user:view")
    public String index() {
        return "index";
    }

    @RequestMapping("/list")
    @RequiresPermissions("user:list")
    public String list() {
        return "list";
    }
}

上面的代码中,@RequiresPermissions注解表示需要该权限才能访问该接口。

5. 测试

最后,需要对整个应用进行测试。可以通过启动SpringBoot应用,然后访问相关接口来进行测试。以下是一个测试的示例:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerTest {

    private static final String username = "admin";
    private static final String password = "123456";

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void testLogin() {
        ResponseEntity<String> response = restTemplate.postForEntity("/login?username={username}&password={password}", null, String.class, username, password);
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
        Assert.assertEquals("index", response.getBody());
    }

    @Test
    public void testUnauthorized() {
        ResponseEntity<String> response = restTemplate.getForEntity("/unauthorized", String.class);
        Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
    }

    @Test
    public void testLogout() {
        ResponseEntity<String> response = restTemplate.getForEntity("/logout", String.class);
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
        Assert.assertEquals("<html><body>欢迎登录</body></html>", response.getBody());
    }

    @Test
    public void testIndex() {
        ResponseEntity<String> response = restTemplate.getForEntity("/", String.class);
        Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());

        response = restTemplate.postForEntity("/login?username={username}&password={password}", null, String.class, username, password);
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());

        response = restTemplate.getForEntity("/", String.class);
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
        Assert.assertEquals("<html><body>首页</body></html>", response.getBody());
    }

    @Test
    public void testList() {
        ResponseEntity<String> response = restTemplate.getForEntity("/list", String.class);
        Assert.assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());

        response = restTemplate.postForEntity("/login?username={username}&password={password}", null, String.class, username, password);
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());

        response = restTemplate.getForEntity("/list", String.class);
        Assert.assertEquals(HttpStatus.OK, response.getStatusCode());
        Assert.assertEquals("<html><body>列表页</body></html>", response.getBody());
    }
}

上面的测试代码中,使用了SpringBoot提供的TestRestTemplate来进行接口测试。

这样就完成了整个应用的搭建和测试。

本文标题为:SpringBoot整合Shiro的代码详解

基础教程推荐