Spring Boot 集成 Apache Shiro:实现安全认证与授权

Spring Boot 集成 Apache Shiro:实现安全认证与授权

在现代 Web 应用开发中,安全性和权限管理是至关重要的。Apache Shiro 是一个强大且易用的 Java 安全框架,提供身份验证、授权、加密和会话管理等功能。Spring Boot 集成 Apache Shiro 可以快速为应用添加安全防护,同时保持代码的简洁性和可维护性。本文将详细介绍如何在 Spring Boot 项目中集成 Apache Shiro,并实现基本的身份验证和授权功能。


一、环境准备

1. 创建 Spring Boot 项目

使用 Spring Initializr 创建一个基础的 Spring Boot 项目,添加 Spring Web 依赖。

2. 添加 Shiro 依赖

pom.xml 中添加 Apache Shiro 的相关依赖:

<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- Apache Shiro Spring Boot Starter -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>1.10.0</version> <!-- 请根据项目需求选择合适的版本 -->
    </dependency>

    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

二、配置 Shiro

1. 创建自定义 Realm

自定义 Realm 是 Shiro 与应用数据交互的桥梁,用于验证用户身份和授权。以下是一个简单的自定义 Realm 示例:

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class MyRealm extends AuthorizingRealm {
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
        if ("admin".equals(username)) {
            authInfo.addRole("admin");
            authInfo.addStringPermission("user:manage");
        }
        return authInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());
        if ("admin".equals(username) && "123456".equals(password)) {
            return new SimpleAuthenticationInfo(username, password, getName());
        }
        throw new IncorrectCredentialsException("用户名或密码错误");
    }
}

2. 配置 Shiro 安全管理器

创建一个配置类 ShiroConfig,配置 Shiro 的核心组件:

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean
    public MyRealm myRealm() {
        return new MyRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/login", "anon"); // 登录页面无需认证
        filterChainDefinitionMap.put("/register", "anon"); // 注册页面无需认证
        filterChainDefinitionMap.put("/logout", "logout"); // 退出登录
        filterChainDefinitionMap.put("/**", "authc"); // 其他页面需要认证

        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        bean.setLoginUrl("/login"); // 设置登录页面
        bean.setSuccessUrl("/home"); // 设置登录成功后的跳转页面
        bean.setUnauthorizedUrl("/403"); // 设置未授权页面

        return bean;
    }
}

三、实现身份验证和授权

1. 创建登录控制器

创建一个控制器处理用户登录请求:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {
    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            currentUser.login(token);
            return "登录成功";
        } catch (UnknownAccountException uae) {
            return "用户名不存在";
        } catch (IncorrectCredentialsException ice) {
            return "密码错误";
        } catch (AuthenticationException ae) {
            return "认证失败: " + ae.getMessage();
        }
    }
}

2. 创建受保护的接口

创建一个受保护的接口,只有经过身份验证的用户才能访问:

好的,继续完成上文的内容。

---

### 2. 创建受保护的接口

创建一个受保护的接口,只有经过身份验证的用户才能访问:

```java
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProtectedController {
    @GetMapping("/home")
    public String home() {
        return "欢迎来到主页!";
    }

    @GetMapping("/admin")
    @RequiresRoles("admin")
    public String admin() {
        return "欢迎管理员!";
    }

    @GetMapping("/manage-users")
    @RequiresPermissions("user:manage")
    public String manageUsers() {
        return "用户管理页面";
    }
}

在上述代码中,@RequiresRoles@RequiresPermissions 注解用于声明式地控制访问权限。只有具备相应角色或权限的用户才能访问这些接口。


四、实现退出登录

ShiroConfig 中,我们已经配置了 /logout 路径映射到 logout 功能。接下来,实现退出登录的逻辑:

import org.apache.shiro.SecurityUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LogoutController {
    @GetMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "已退出登录";
    }
}

当用户访问 /logout 时,Shiro 会自动清理当前用户的会话信息,并跳转到登录页面。


五、自定义登录页面和错误页面

1. 自定义登录页面

创建一个简单的登录页面 login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
    <h1>登录</h1>
    <form action="/login" method="post">
        <label for="username">用户名:</label>
        <input type="text" id="username" name="username" required><br>
        <label for="password">密码:</label>
        <input type="password" id="password" name="password" required><br>
        <button type="submit">登录</button>
    </form>
</body>
</html>

2. 自定义错误页面

创建一个错误页面 403.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>403 Forbidden</title>
</head>
<body>
    <h1>403 Forbidden</h1>
    <p>您没有权限访问此页面。</p>
</body>
</html>

六、动态权限管理

在实际项目中,权限信息通常存储在数据库中。Shiro 支持动态加载权限信息,可以通过自定义 Realm 实现。

1. 数据库设计

假设我们有以下表结构:

  • 用户表 (users): 存储用户信息。
  • 角色表 (roles): 存储角色信息。
  • 权限表 (permissions): 存储权限信息。
  • 用户角色关系表 (user_roles): 存储用户与角色的关联。
  • 角色权限关系表 (role_permissions): 存储角色与权限的关联。

2. 修改自定义 Realm

从数据库加载用户、角色和权限信息:

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();

        // 查询用户的角色和权限
        List<String> roles = userService.getRolesByUsername(username);
        List<String> permissions = userService.getPermissionsByUsername(username);

        authInfo.addRoles(roles);
        authInfo.addStringPermissions(permissions);

        return authInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());

        User user = userService.getUserByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("用户不存在");
        }
        if (!user.getPassword().equals(password)) {
            throw new IncorrectCredentialsException("密码错误");
        }

        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

3. 服务层实现

实现 UserService,从数据库加载用户、角色和权限信息:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public List<String> getRolesByUsername(String username) {
        return userRepository.findRolesByUsername(username);
    }

    public List<String> getPermissionsByUsername(String username) {
        return userRepository.findPermissionsByUsername(username);
    }
}

4. 数据库访问层

创建 UserRepository,实现数据库访问逻辑:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Set;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);

    @Query("SELECT r.name FROM Role r JOIN User u ON u.id = r.userId WHERE u.username = ?1")
    List<String> findRolesByUsername(String username);

    @Query("SELECT p.name FROM Permission p JOIN Role r ON r.id = p.roleId JOIN User u ON u.id = r.userId WHERE u.username = ?1")
    List<String> findPermissionsByUsername(String username);
}

七、总结

通过本文,我们详细介绍了如何在 Spring Boot 项目中集成 Apache Shiro,实现身份验证、授权和动态权限管理。Shiro 提供了强大的安全功能,结合 Spring Boot 的自动配置和依赖管理,可以快速为应用添加安全防护。

希望本文能帮助你更好地理解和使用 Spring Boot 集成 Shiro。如果你有任何疑问或建议,欢迎在评论区留言交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值