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

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

maynowei6个月前 (09-13)技术知识54

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 的登录认证和权限管理,而且是 加盐加密版 + 注解控制版 + 可扩展生产版

相关文章

Shopee新手指南:Shopee卖家中心用户界面介绍

1.Shopee各站点前台网页链接:2.Shopee各站点后台网页链接3.Shopee APP下载:安卓版下载链接:https://pan.baidu.com/s/1eSp8M1k#list/path...

CPU「离奇」飙到 100%!开发者挖出 Linux 内核 16 年老 Bug:这么多年竟无人发现?

【CSDN 编者按】每一次对旧设备的升级都仿佛是一场跨越时代的冒险。本文作者致力于将基于 PXA166 的 Chumby 8 设备从 Linux 2.6.28 版本升级到现代 6.x 版本,然而,在看...

Go 语言中的 RWMutex 源码解析、使用场景及应用分析

Go 语言标准库的 sync 包提供了一些非常重要的并发原语来帮助程序开发者处理并发任务。本文将详细解析 sync 包中的 RWMutex,即读写互斥锁(Reader/Writer Mutex),并结...

Linux C++实现多线程同步的四种方式(超级详细)

背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题?通过多线程模拟多窗口售票为例:#include <iostream>#include<pthread.h>#inc...

go语言并发原语RWMutex实现原理及闭坑指南

1.RWMutex常用方法Lock/UnlockRLock/RUnlockRLocker 为读操作返回一个Locker接 口的对象2. RWMutex使用方法 func main() { var c...

一个快要被忘记的数据库开发岗位,但应该被尊重

数据库测试,似乎是被人遗忘的数据库职业,但依然是不错的选择。底下是我在某站找的招聘启事,就连蚂蚁金服都在积极寻找数据库测试人:要说我经历的项目,大大小小也有几十个,从 C/S, B/S, 再到 B/C...