当前位置:首页 > 技术知识 > 正文内容

Spring Boot 3.X 整合 Shiro - 登录认证和权限管理

maynowei8个月前 (09-13)技术知识86

Spring Boot 3.X 整合 Shiro - 登录认证和权限管理

Apache Shiro 是一个强大且易用的 Java 安全框架,提供认证、授权、加密、会话管理等功能。本文将手把手教你在 Spring Boot 3.X 项目中整合 Shiro,实现 安全的登录认证 + 角色权限管理,并且加入生产可用的加密和缓存优化。


1. 添加依赖

在 pom.xml 中添加必要依赖:

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

    <!-- Shiro Spring Boot Starter -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>

    <!-- JPA + MySQL -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Lombok(可选) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

2. 数据库建表 SQL

CREATE TABLE sys_user (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL UNIQUE,
  password VARCHAR(100) NOT NULL,
  salt VARCHAR(50),
  status TINYINT DEFAULT 1 COMMENT '0-锁定 1-正常'
);

CREATE TABLE sys_role (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(100)
);

CREATE TABLE sys_permission (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50),
  permission VARCHAR(100),
  url VARCHAR(200)
);

CREATE TABLE sys_user_role (
  user_id BIGINT,
  role_id BIGINT,
  PRIMARY KEY (user_id, role_id)
);

CREATE TABLE sys_role_permission (
  role_id BIGINT,
  permission_id BIGINT,
  PRIMARY KEY (role_id, permission_id)
);

3. Shiro 核心配置

@Configuration
public class ShiroConfig {

    // 密码加密配置
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256");
        matcher.setHashIterations(1024);
        matcher.setStoredCredentialsHexEncoded(true);
        return matcher;
    }

    // 自定义 Realm
    @Bean
    public CustomRealm customRealm() {
        CustomRealm realm = new CustomRealm();
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    // 安全管理器
    @Bean
    public SecurityManager securityManager(CustomRealm customRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm);
        return securityManager;
    }

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

    // Shiro 过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);

        Map<String, String> map = new LinkedHashMap<>();
        map.put("/static/**", "anon");
        map.put("/login", "anon");
        map.put("/doLogin", "anon");
        map.put("/logout", "logout");
        map.put("/**", "authc");
        factoryBean.setFilterChainDefinitionMap(map);

        factoryBean.setLoginUrl("/login");
        factoryBean.setSuccessUrl("/index");
        factoryBean.setUnauthorizedUrl("/403");
        return factoryBean;
    }
}

4. 自定义 Realm(安全版)

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(userService.findRoles(username));
        info.setStringPermissions(userService.findPermissions(username));
        return info;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        User user = userService.findByUsername(username);
        if (user == null) throw new UnknownAccountException("用户不存在");
        if (user.getStatus() == 0) throw new LockedAccountException("账号已锁定");

        return new SimpleAuthenticationInfo(
                user.getUsername(),
                user.getPassword(),
                ByteSource.Util.bytes(user.getSalt()), // 加盐
                getName()
        );
    }
}

5. 用户服务层

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

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

    public Set<String> findRoles(String username) {
        return userRepository.findRolesByUsername(username);
    }

    public Set<String> findPermissions(String username) {
        return userRepository.findPermissionsByUsername(username);
    }
}

6. 登录控制器(支持错误提示)

@Controller
public class LoginController {

    @GetMapping("/login")
    public String loginPage() {
        return "login";
    }

    @PostMapping("/doLogin")
    public String doLogin(@RequestParam String username,
                         @RequestParam String password,
                         Model model) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            subject.login(token);
            return "redirect:/index";
        } catch (UnknownAccountException e) {
            model.addAttribute("msg", "用户名错误");
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("msg", "密码错误");
        } catch (LockedAccountException e) {
            model.addAttribute("msg", "账号被锁定");
        } catch (AuthenticationException e) {
            model.addAttribute("msg", "认证失败");
        }
        return "login";
    }

    @GetMapping("/logout")
    public String logout() {
        SecurityUtils.getSubject().logout();
        return "redirect:/login";
    }
}

7. 权限控制示例

@Controller
public class UserController {

    @GetMapping("/index")
    public String index() {
        return "index";
    }

    @GetMapping("/admin")
    @RequiresRoles("admin")
    public String adminPage() {
        return "admin";
    }

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

8. 前端登录页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<div th:if="${msg}" style="color: red;" th:text="${msg}"></div>
<form action="/doLogin" method="post">
    用户名: <input type="text" name="username"><br>
    密码: <input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

9. 生产环境优化建议

  1. 密码加密:使用 SHA-256 + 盐 + 多次迭代(已在本文配置)。
  2. 缓存:集成 Redis 缓存角色和权限,避免频繁查库。
  3. 前后端分离:用自定义 Filter 返回 JSON,而不是跳转页面。
  4. Remember Me:支持免登录功能,提升体验。
  5. 会话管理:配置 Shiro SessionManager 或整合 Spring Session。

这样改完后,只需要导入 SQL、运行项目,就能直接体验 Spring Boot 3.x + Shiro 的登录认证和权限管理,而且是 加盐加密版 + 注解控制版 + 可扩展生产版

相关文章

验证码,除了 12306,我还没有服过谁

为了防止暴力注册或爬虫爬取等机器请求,需要验证操作者是人还是机器,便有了验证码这个设计。本文作者主要介绍了如何使用 Axure 来设计一个动态的图形验证码,一起来学习一下吧。在软件设计中,为了防止暴力...

高效办公,你值得拥有之原型工具AXURE篇

简介 Axure RP是美国Axure Software Solution公司旗舰产品,是一个专业的快速原型设计工具,让负责定义需求和规格、设计功能和界面的专家能够快速创建应用软件或Web网站的线框图...

Linux系统编程—互斥量mutex(linux 互斥量)

##互斥量mutex前文提到,系统中如果存在资源共享,线程间存在竞争,并且没有合理的同步机制的话,会出现数据混乱的现象。为了实现同步机制,Linux中提供了多种方式,其中一种方式为互斥锁mutex(也...

什么?Java 中的锁还有状态?(java中的锁都有哪些类型)

线程如果锁住了某个资源,致使其他线程无法访问的这种锁被称为悲观锁,相反,线程不锁住资源的锁被称为乐观锁,而自旋锁是基于 CAS 机制实现的,CAS又是乐观锁的一种实现,那么对于锁来说,多个线程同步访问...

c++ 继承简介(c++继承的概念)

24.1 — 继承简介2024 年 6 月 5 日在上一章中,我们讨论了对象组合,即从更简单的类和类型构建复杂类。对象组合非常适合构建与其部分具有“has-a”关系的新对象。但是,对象组合只是 C++...

Oracle标准化部署手册(oracle19c客户端)

很久之前写过一篇11g的windows安装手册, 这次是19c的windows安装手册,面向没有数据库安装部署经验的开发人员或想学习数据库的新手。希望能给想从事dba的入门人员小小的帮助。 毕竟每个高...