导读:本期聚焦于赵六创作的《Java安全框架Shiro入门详解:SpringBoot整合Shiro完整代码与常见错误避坑指南》,敬请观看详情。权限认证该怎么做?Apache Shiro作为Java领域轻量级的安全框架,凭借简单易用的API吸引了大量开发者。本文从Shiro的核心概念讲起,先介绍Subject、SecurityManager、Realm三大组件各自承担的职责,再通过完整代码演示SpringBoot整合Shiro的全过程,包括依赖引入、自定义Realm、ShiroConfig配置类的编写,以及登录接口的实现。认证与授权的执行流程会在源码层面做简要剖析,帮助理解为什么Realm是整个框架的核心。文章最后总结了整合过程中的高频错误,比如过滤器顺序错误导致接口被拦截、密码比对失败、授权注解不生效等问题,并给出对应的解决方案,适合初学者快速上手并少走弯路。

Shiro是Apache基金会旗下的开源安全框架,核心功能覆盖身份认证、授权、加密和会话管理四大块。与Spring Security相比,Shiro的架构更轻量,学习成本更低,不依赖Spring容器也能独立运行,因此在中小型项目和老项目改造中非常常见。本文将从核心概念入手,逐步演示SpringBoot整合Shiro的完整流程,并总结开发中最容易踩到的坑。

Java安全框架Shiro入门详解:SpringBoot整合Shiro完整代码与常见错误避坑指南

一、Shiro的核心架构与三大组件

在学习任何框架之前,理解它的整体架构比记住API更重要。Shiro的整体设计围绕三个核心对象展开:Subject、SecurityManager和Realm。三者关系可以理解为:应用程序代码只与Subject交互,Subject把请求委托给SecurityManager,SecurityManager再通过Realm获取真实的用户数据。

Subject直译为主题,代表当前与系统交互的用户,它本质上是一个门面对象。我们在代码中调用subject.login(token)时,Subject本身不做任何校验,而是把token交给SecurityManager处理。这个设计让业务代码与安全逻辑完全解耦。

SecurityManager是Shiro的大脑,负责调度内部各个组件,包括认证器Authenticator、授权器Authorizer、会话管理器SessionManager和缓存管理器。一个应用只需要配置一个SecurityManager实例,它是所有安全操作的入口。

Realm是数据桥梁,负责从数据库或其他数据源中读取用户信息、角色和权限。Shiro并不知道你的用户数据存在哪里,开发者必须自定义Realm并实现doGetAuthenticationInfodoGetAuthorizationInfo两个方法。前者提供认证数据,后者提供授权数据,这也是整个框架中唯一需要我们写业务逻辑的地方。

二、SpringBoot整合Shiro的完整代码实现

下面通过一个完整的例子演示整合过程。假设我们有一个用户表,包含用户名和加密后的密码,需要实现登录认证和基于角色的授权。

第一步是引入依赖,在pom.xml中添加shiro-spring-boot-starter:

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

第二步编写自定义Realm,这是整合中最核心的类。注意密码比对时使用MD5加盐,盐值要与注册时保持一致:

public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    // 授权:查询当前用户的角色和权限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 实际项目中从数据库查询,这里演示写死
        info.addRole("admin");
        info.addStringPermission("user:delete");
        return info;
    }

    // 认证:验证用户名和密码
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        String username = (String) token.getPrincipal();
        // 模拟查询数据库,查不到用户直接返回null表示账号不存在
        User user = userService.findByUsername(username);
        if (user == null) {
            return null;
        }
        // 第一个参数是用户名,第二个是数据库中的密码,第三个是盐值,第四个是Realm名称
        return new SimpleAuthenticationInfo(
                username,
                user.getPassword(),
                ByteSource.Util.bytes(user.getSalt()),
                getName()
        );
    }
}

第三步编写Shiro配置类,将SecurityManager和ShiroFilterFactoryBean注册到容器中。过滤器链的定义顺序非常关键,具体注意事项下一节详细说明:

@Configuration
public class ShiroConfig {

    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager(UserRealm userRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 设置自定义的密码匹配器,MD5加盐两次
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(2);
        userRealm.setCredentialsMatcher(matcher);
        manager.setRealm(userRealm);
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        // 过滤器链,注意顺序:具体的路径写在前面,通配路径写在后面
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/login", "anon");
        map.put("/css/**", "anon");
        map.put("/user/**", "authc");
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
}

最后是登录接口,通过Subject执行登录。登录失败时Shiro会抛出具体的异常类型,可以据此给用户返回友好提示:

@RestController
public class LoginController {

    @PostMapping("/login")
    public Result login(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return Result.success("登录成功");
        } catch (UnknownAccountException e) {
            return Result.error("账号不存在");
        } catch (IncorrectCredentialsException e) {
            return Result.error("密码错误");
        } catch (LockedAccountException e) {
            return Result.error("账号被锁定");
        }
    }

    @RequiresRoles("admin")
    @GetMapping("/admin/data")
    public Result adminData() {
        return Result.success("管理员专属数据");
    }
}

三、认证与授权的执行流程剖析

当调用subject.login(token)后,内部流程是:Subject把token交给SecurityManager,SecurityManager调用Authenticator进行认证,Authenticator再调用Realm的doGetAuthenticationInfo方法获取数据库中的用户信息,最后由CredentialsMatcher比对提交的密码与数据库密码是否一致。

密码比对失败会抛出IncorrectCredentialsException,账号不存在时如果Realm返回null会抛出UnknownAccountException。如果我们没有配置CredentialsMatcher,默认使用的是明文比对,而数据库中存的是加密后的密文,必然导致登录永远失败,这是初学者最常见的错误之一。

授权流程类似。当访问标注了@RequiresRoles@RequiresPermissions的方法时,Shiro会通过AOP拦截请求,调用Realm的doGetAuthorizationInfo获取当前用户的角色权限集合,再校验是否包含所需权限。授权数据默认有缓存,修改用户权限后需要调用clearCachedAuthorizationInfo清理缓存才能立即生效。

四、常见错误与注意事项总结

第一,过滤器链顺序错误。Shiro按定义顺序逐个匹配,一旦匹配成功就不会继续往下走。如果把/**写在前面,后面所有的放行规则都会失效。正确做法是具体的路径在前,宽泛的路径在后,并且使用LinkedHashMap保证顺序。

第二,前后端分离项目中使用注解不生效。Shiro的@RequiresRoles等注解依赖AOP代理,需要手动开启注解支持。另外要注意,Controller内部方法自调用时注解不会生效,因为绕过了代理对象,这一点与Spring事务注解失效的原理相同。

第三,Session问题。前后端分离架构下,服务端不保存Session,Shiro默认的SessionDao无法工作。解决方案是改用JWT无状态方案,重写Realm时从请求头中解析token,或者将Session存储到Redis中实现分布式会话。

第四,密码加盐比对失败。注册时加密用的盐值、加密次数必须与登录时CredentialsMatcher中配置的完全一致,任何一项不匹配都会导致IncorrectCredentialsException。建议将盐值存在用户表中,而不是写死在代码里。

第五,静态资源被拦截。CSS、JS、图片等资源如果没有配置anon放行,登录页面会出现样式丢失的问题。可以在过滤器链中添加map.put("/static/**", "anon")之类的规则。

掌握以上内容后,基本的认证授权场景就都能应对了。如果项目规模较大,建议进一步学习Shiro的缓存机制和多Realm配置,这些进阶特性在权限体系复杂时会明显提升系统的可维护性。

Shiro SpringBoot整合Shiro Java安全框架修改时间:2026-09-02 19:27:11

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260902/49097.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。