找回密码
 立即注册
首页 业界区 业界 SpringSecurity、Shiro 和 Sa-Token,选哪个更好? ...

SpringSecurity、Shiro 和 Sa-Token,选哪个更好?

边书仪 5 天前
前言

今天我们来聊聊一个让很多Java开发者纠结的技术选型问题:Spring Security、Apache Shiro和Sa-Token,这3个主流安全框架到底该选哪个?
有些小伙伴在工作中可能遇到过这样的场景:新项目启动会上,架构师坚持要用Spring Security,团队里的老将却说Shiro更简单实用,而年轻的同事则力荐Sa-Token这个后起之秀。
大家各执一词,都有道理,到底该听谁的?
今天这篇文章就跟大家一起聊聊这个话题,希望对你会有所帮助。
1 我们为什么需要安全框架?

在深入对比之前,我们先要理解:为什么不能自己手写安全逻辑,而非要用框架?
想象一下,如果你要为一个电商系统实现权限控制,你需要处理:
  1. // 手写权限控制的典型痛点
  2. public class ManualSecurityExample {
  3.    
  4.     // 1. 每个方法都要写重复的权限校验
  5.     public void updateProduct(Long productId, ProductDTO dto) {
  6.         // 检查用户是否登录
  7.         User user = getCurrentUser();
  8.         if (user == null) {
  9.             throw new UnauthorizedException("请先登录");
  10.         }
  11.         
  12.         // 检查用户是否有编辑权限
  13.         if (!user.hasPermission("product:update")) {
  14.             throw new ForbiddenException("没有操作权限");
  15.         }
  16.         
  17.         // 检查是否是自己的商品(数据级权限)
  18.         Product product = productService.getById(productId);
  19.         if (!product.getOwnerId().equals(user.getId())) {
  20.             throw new ForbiddenException("只能修改自己的商品");
  21.         }
  22.         
  23.         // 实际业务逻辑...
  24.         productService.update(productId, dto);
  25.     }
  26.    
  27.     // 2. 每个Controller都要写登录检查
  28.     // 3. 需要自己管理Session/Token
  29.     // 4. 密码加密、CSRF防护都要自己实现
  30.     // 5. 审计日志、安全事件处理...
  31. }
复制代码
看到问题了吗?
安全逻辑会像“幽灵代码”一样渗透到业务的每个角落,导致:

  • 代码重复率高
  • 业务逻辑和安全逻辑耦合
  • 难以统一维护和升级
  • 容易遗漏安全防护点
安全框架的价值,就是把这些问题抽象化、标准化、自动化
下面这个示意图展示了安全框架如何将安全关注点从业务代码中解耦出来:
1.png

理解了安全框架的价值,接下来我们深入分析这三个主流选项。
2 Spring Security:企业级的安全“瑞士军刀”

2.1 Spring Security是什么?

Spring Security是Spring官方提供的安全框架,可以说是Spring生态中的“御林军”。
它不仅仅是一个权限控制框架,更是一个全面的安全解决方案
2.2 核心架构:过滤器链的极致运用

Spring Security的核心是过滤器链(Filter Chain)
当一个请求到达时,它会经过一系列安全过滤器,每个过滤器负责特定的安全功能:
2.png

2.3 快速搭建一个安全的REST API
  1. // 1. 基础配置类
  2. @Configuration
  3. @EnableWebSecurity
  4. public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
  5.    
  6.     @Override
  7.     protected void configure(HttpSecurity http) throws Exception {
  8.         http
  9.             // 禁用CSRF(REST API通常不需要)
  10.             .csrf().disable()
  11.             
  12.             // 授权配置
  13.             .authorizeRequests()
  14.                 .antMatchers("/api/public/**").permitAll()  // 公开接口
  15.                 .antMatchers("/api/admin/**").hasRole("ADMIN")  // 需要管理员角色
  16.                 .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")  // 需要用户角色
  17.                 .anyRequest().authenticated()  // 其他所有请求需要认证
  18.             
  19.             // 表单登录配置(前后端分离时通常用不上)
  20.             .and()
  21.             .formLogin().disable()
  22.             
  23.             // 基础认证配置
  24.             .httpBasic()
  25.             
  26.             // 异常处理
  27.             .and()
  28.             .exceptionHandling()
  29.                 .authenticationEntryPoint(restAuthenticationEntryPoint())  // 未认证处理
  30.                 .accessDeniedHandler(restAccessDeniedHandler());  // 权限不足处理
  31.     }
  32.    
  33.     // 2. 用户详情服务(从数据库加载用户)
  34.     @Bean
  35.     public UserDetailsService userDetailsService() {
  36.         return username -> {
  37.             // 这里实际应该查询数据库
  38.             if ("admin".equals(username)) {
  39.                 return User.withUsername("admin")
  40.                     .password(passwordEncoder().encode("admin123"))
  41.                     .roles("ADMIN")
  42.                     .build();
  43.             } else if ("user".equals(username)) {
  44.                 return User.withUsername("user")
  45.                     .password(passwordEncoder().encode("user123"))
  46.                     .roles("USER")
  47.                     .build();
  48.             }
  49.             throw new UsernameNotFoundException("用户不存在: " + username);
  50.         };
  51.     }
  52.    
  53.     // 3. 密码编码器
  54.     @Bean
  55.     public PasswordEncoder passwordEncoder() {
  56.         return new BCryptPasswordEncoder();
  57.     }
  58.    
  59.     // 4. REST API认证入口点
  60.     @Bean
  61.     public AuthenticationEntryPoint restAuthenticationEntryPoint() {
  62.         return (request, response, authException) -> {
  63.             response.setContentType(MediaType.APPLICATION_JSON_VALUE);
  64.             response.setStatus(HttpStatus.UNAUTHORIZED.value());
  65.             response.getWriter().write(
  66.                 "{"code": 401, "message": "未认证,请先登录"}"
  67.             );
  68.         };
  69.     }
  70. }
  71. // 5. 在Controller中使用安全注解
  72. @RestController
  73. @RequestMapping("/api")
  74. public class ProductController {
  75.    
  76.     @GetMapping("/public/products")
  77.     public List<Product> getPublicProducts() {
  78.         // 公开接口,无需认证
  79.         return productService.getAllProducts();
  80.     }
  81.    
  82.     @GetMapping("/user/products")
  83.     @PreAuthorize("hasRole('USER')")  // 需要USER角色
  84.     public List<Product> getUserProducts() {
  85.         // 获取当前认证用户
  86.         Authentication auth = SecurityContextHolder.getContext().getAuthentication();
  87.         String username = auth.getName();
  88.         
  89.         return productService.getProductsByOwner(username);
  90.     }
  91.    
  92.     @PostMapping("/admin/products")
  93.     @PreAuthorize("hasRole('ADMIN')")  // 需要ADMIN角色
  94.     public Product createProduct(@RequestBody ProductDTO dto) {
  95.         return productService.createProduct(dto);
  96.     }
  97.    
  98.     @DeleteMapping("/admin/products/{id}")
  99.     @PreAuthorize("hasPermission(#id, 'product', 'delete')")  // 方法级权限控制
  100.     public void deleteProduct(@PathVariable Long id) {
  101.         productService.deleteProduct(id);
  102.     }
  103. }
复制代码
Spring Security的优势与痛点

优势:

  • Spring生态原生支持:与Spring Boot、Spring Cloud无缝集成
  • 功能全面:认证、授权、防护(CSRF、CORS、点击劫持等)一应俱全
  • 高度可定制:几乎每个组件都可以自定义或替换
  • 社区强大:Spring官方维护,文档完善,社区活跃
  • 企业级特性:OAuth2、SAML、LDAP等企业级集成支持
痛点:

  • 学习曲线陡峭:概念复杂,配置繁琐
  • 过度设计感:简单需求也需要复杂配置
  • 调试困难:过滤器链复杂,问题定位困难
  • 性能开销:完整的过滤器链带来一定性能损失
适用场景:

  • 大型企业级应用
  • 需要与Spring生态深度集成的项目
  • 需要OAuth2、LDAP等企业级认证协议的项目
  • 团队有Spring Security经验的场景
有些小伙伴刚开始学Spring Security时,可能会被它复杂的概念搞晕,比如:SecurityContext、Authentication、UserDetails、GrantedAuthority等等。
但一旦掌握了它的设计哲学,你会发现它真的很强大。
3 Apache Shiro:简单直观的“轻骑兵”

3.1 Shiro是什么?

Apache Shiro是一个功能强大且易于使用的Java安全框架,它的设计哲学是:简化应用安全,让安全变得更简单
如果说Spring Security是重型坦克,那么Shiro就是灵活机动的轻骑兵。
3.2 核心架构:四大核心概念

Shiro的架构围绕四个核心概念构建:
3.png

3.3 快速实现基于URL的权限控制
  1. // 1. Shiro配置类
  2. @Configuration
  3. public class ShiroConfig {
  4.    
  5.     // 创建ShiroFilterFactoryBean
  6.     @Bean
  7.     public ShiroFilterFactoryBean shiroFilterFactoryBean(
  8.             SecurityManager securityManager) {
  9.         ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
  10.         factoryBean.setSecurityManager(securityManager);
  11.         
  12.         // 设置登录页面
  13.         factoryBean.setLoginUrl("/login");
  14.         // 设置未授权页面
  15.         factoryBean.setUnauthorizedUrl("/unauthorized");
  16.         
  17.         // 配置拦截规则
  18.         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
  19.         
  20.         // 静态资源放行
  21.         filterChainDefinitionMap.put("/static/**", "anon");
  22.         filterChainDefinitionMap.put("/css/**", "anon");
  23.         filterChainDefinitionMap.put("/js/**", "anon");
  24.         
  25.         // 公开接口
  26.         filterChainDefinitionMap.put("/api/public/**", "anon");
  27.         filterChainDefinitionMap.put("/login", "anon");
  28.         
  29.         // 需要认证的接口
  30.         filterChainDefinitionMap.put("/api/user/**", "authc");
  31.         filterChainDefinitionMap.put("/api/admin/**", "authc, roles[admin]");
  32.         
  33.         // 需要特定权限的接口
  34.         filterChainDefinitionMap.put("/api/products/create", "authc, perms[product:create]");
  35.         filterChainDefinitionMap.put("/api/products/delete/*", "authc, perms[product:delete]");
  36.         
  37.         // 其他所有请求需要认证
  38.         filterChainDefinitionMap.put("/**", "authc");
  39.         
  40.         factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  41.         return factoryBean;
  42.     }
  43.    
  44.     // 创建SecurityManager
  45.     @Bean
  46.     public SecurityManager securityManager() {
  47.         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  48.         
  49.         // 设置Realm
  50.         securityManager.setRealm(customRealm());
  51.         
  52.         // 设置Session管理器
  53.         securityManager.setSessionManager(sessionManager());
  54.         
  55.         // 设置缓存管理器
  56.         securityManager.setCacheManager(cacheManager());
  57.         
  58.         return securityManager;
  59.     }
  60.    
  61.     // 自定义Realm(连接安全数据源)
  62.     @Bean
  63.     public Realm customRealm() {
  64.         CustomRealm realm = new CustomRealm();
  65.         
  66.         // 设置密码匹配器
  67.         HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
  68.         credentialsMatcher.setHashAlgorithmName("SHA-256");
  69.         credentialsMatcher.setHashIterations(1024);
  70.         credentialsMatcher.setStoredCredentialsHexEncoded(false);
  71.         realm.setCredentialsMatcher(credentialsMatcher);
  72.         
  73.         // 开启缓存
  74.         realm.setCachingEnabled(true);
  75.         realm.setAuthenticationCachingEnabled(true);
  76.         realm.setAuthenticationCacheName("authenticationCache");
  77.         realm.setAuthorizationCachingEnabled(true);
  78.         realm.setAuthorizationCacheName("authorizationCache");
  79.         
  80.         return realm;
  81.     }
  82. }
  83. // 2. 自定义Realm实现
  84. public class CustomRealm extends AuthorizingRealm {
  85.    
  86.     @Autowired
  87.     private UserService userService;
  88.    
  89.     // 认证逻辑:验证用户身份
  90.     @Override
  91.     protected AuthenticationInfo doGetAuthenticationInfo(
  92.             AuthenticationToken token) throws AuthenticationException {
  93.         
  94.         UsernamePasswordToken upToken = (UsernamePasswordToken) token;
  95.         String username = upToken.getUsername();
  96.         
  97.         // 从数据库查询用户
  98.         User user = userService.findByUsername(username);
  99.         if (user == null) {
  100.             throw new UnknownAccountException("用户不存在");
  101.         }
  102.         
  103.         if (!user.isEnabled()) {
  104.             throw new DisabledAccountException("用户已被禁用");
  105.         }
  106.         
  107.         // 返回认证信息
  108.         return new SimpleAuthenticationInfo(
  109.             user, // 身份 principal
  110.             user.getPassword(), // 凭证 credentials
  111.             getName() // realm name
  112.         );
  113.     }
  114.    
  115.     // 授权逻辑:获取用户的角色和权限
  116.     @Override
  117.     protected AuthorizationInfo doGetAuthorizationInfo(
  118.             PrincipalCollection principals) {
  119.         
  120.         User user = (User) principals.getPrimaryPrincipal();
  121.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  122.         
  123.         // 添加角色
  124.         Set<String> roles = userService.findRolesByUserId(user.getId());
  125.         authorizationInfo.setRoles(roles);
  126.         
  127.         // 添加权限
  128.         Set<String> permissions = userService.findPermissionsByUserId(user.getId());
  129.         authorizationInfo.setStringPermissions(permissions);
  130.         
  131.         return authorizationInfo;
  132.     }
  133. }
  134. // 3. 在Controller中使用Shiro
  135. @RestController
  136. @RequestMapping("/api")
  137. public class ProductController {
  138.    
  139.     @GetMapping("/products")
  140.     public List<Product> getProducts() {
  141.         // 获取当前用户
  142.         Subject currentUser = SecurityUtils.getSubject();
  143.         
  144.         // 检查是否已认证
  145.         if (!currentUser.isAuthenticated()) {
  146.             throw new UnauthorizedException("请先登录");
  147.         }
  148.         
  149.         // 检查是否有权限
  150.         if (!currentUser.isPermitted("product:view")) {
  151.             throw new ForbiddenException("没有查看权限");
  152.         }
  153.         
  154.         // 执行业务逻辑
  155.         return productService.getAllProducts();
  156.     }
  157.    
  158.     @PostMapping("/products")
  159.     public Product createProduct(@RequestBody ProductDTO dto) {
  160.         Subject currentUser = SecurityUtils.getSubject();
  161.         
  162.         // 使用Shiro的权限注解(需要AOP支持)
  163.         currentUser.checkPermission("product:create");
  164.         
  165.         // 或者使用编程式检查
  166.         // if (!currentUser.isPermitted("product:create")) {
  167.         //     throw new ForbiddenException("没有创建权限");
  168.         // }
  169.         
  170.         return productService.createProduct(dto);
  171.     }
  172.    
  173.     @GetMapping("/admin/dashboard")
  174.     public DashboardVO getAdminDashboard() {
  175.         Subject currentUser = SecurityUtils.getSubject();
  176.         
  177.         // 检查是否具有admin角色
  178.         currentUser.checkRole("admin");
  179.         
  180.         return dashboardService.getAdminDashboard();
  181.     }
  182. }
复制代码
Shiro的优势与痛点

优势:

  • 简单直观:API设计简洁,学习成本低
  • 配置灵活:支持INI、XML、注解等多种配置方式
  • 功能完整:认证、授权、会话管理、加密、缓存等一应俱全
  • 不依赖容器:可以在任何Java环境中运行
  • 易于集成:与Spring、Spring Boot等框架集成简单
痛点:

  • Spring生态整合不够原生:需要额外配置
  • 社区活跃度下降:相比Spring Security,社区维护力度减弱
  • 功能扩展性有限:某些高级功能需要自己实现
  • 文档相对陈旧:部分文档更新不及时
适用场景:

  • 中小型项目,追求快速开发
  • 非Spring项目或Spring生态不重的项目
  • 团队对Spring Security不熟悉
  • 需要简单权限控制的内部系统
有些小伙伴喜欢Shiro的简洁,特别是它的INI配置文件,几行配置就能搞定基本的权限控制。
但当你需要更复杂的功能时,可能会发现需要自己写不少代码。
4 Sa-Token:国产新星的“后起之秀”

4.1 Sa-Token是什么?

Sa-Token是一个轻量级Java权限认证框架,由国内开发者开发。
它的设计理念是:以最少的配置,完成最全面的权限认证功能
在Spring Security和Shiro之外,Sa-Token提供了一种新的选择。
4.2 核心特性:简单而强大

Sa-Token的核心设计哲学可以概括为:“简单、强大、灵活”。
它通过几个核心组件实现了完整的安全控制:
4.png

4.3 5分钟搭建完整权限系统
  1. // 1. 添加依赖(pom.xml)
  2. // <dependency>
  3. //     <groupId>cn.dev33</groupId>
  4. //     sa-token-spring-boot-starter</artifactId>
  5. //     <version>1.34.0</version>
  6. // </dependency>
  7. // 2. 配置文件(application.yml)
  8. // sa-token:
  9. //   token-name: satoken           # token名称
  10. //   timeout: 2592000             # token有效期,单位秒,默认30天
  11. //   active-timeout: -1           # token活跃有效期,-1代表不限制
  12. //   is-concurrent: true          # 是否允许并发登录
  13. //   is-share: true               # 在多人登录同一账号时,是否共享token
  14. //   max-login-count: 12          # 同一账号最大登录数量
  15. //   is-write-header: true        # 是否将token写入响应头
  16. //   token-style: uuid            # token风格
  17. //   is-log: false                # 是否打印操作日志
  18. // 3. 配置类(可选)
  19. @Configuration
  20. public class SaTokenConfig {
  21.    
  22.     // 注册拦截器
  23.     @Bean
  24.     public SaInterceptor saInterceptor() {
  25.         return new SaInterceptor()
  26.             // 校验登录状态,不包含登录接口
  27.             .addPathPatterns("/**")
  28.             .excludePathPatterns("/api/user/login")
  29.             .excludePathPatterns("/api/public/**")
  30.             
  31.             // 权限校验规则
  32.             .check(r -> {
  33.                 // 1. 检查登录状态
  34.                 SaRouter.match("/api/**", () -> {
  35.                     StpUtil.checkLogin();
  36.                 });
  37.                
  38.                 // 2. 角色校验
  39.                 SaRouter.match("/api/admin/**", () -> {
  40.                     StpUtil.checkRole("admin");
  41.                 });
  42.                
  43.                 // 3. 权限校验
  44.                 SaRouter.match("/api/products/create", () -> {
  45.                     StpUtil.checkPermission("product.create");
  46.                 });
  47.                
  48.                 SaRouter.match("/api/products/delete/**", () -> {
  49.                     StpUtil.checkPermission("product.delete");
  50.                 });
  51.             });
  52.     }
  53. }
  54. // 4. 登录认证Controller
  55. @RestController
  56. @RequestMapping("/api/user")
  57. public class UserController {
  58.    
  59.     @PostMapping("/login")
  60.     public ApiResult login(@RequestBody LoginDTO dto) {
  61.         // 1. 验证用户名密码
  62.         User user = userService.findByUsername(dto.getUsername());
  63.         if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
  64.             return ApiResult.error("用户名或密码错误");
  65.         }
  66.         
  67.         // 2. 登录(Sa-Token会自动创建token)
  68.         StpUtil.login(user.getId());
  69.         
  70.         // 3. 获取token信息
  71.         String tokenValue = StpUtil.getTokenValue();
  72.         long tokenTimeout = StpUtil.getTokenTimeout();
  73.         
  74.         // 4. 返回用户信息和token
  75.         LoginVO vo = new LoginVO();
  76.         vo.setUserId(user.getId());
  77.         vo.setUsername(user.getUsername());
  78.         vo.setToken(tokenValue);
  79.         vo.setExpireTime(tokenTimeout);
  80.         
  81.         // 5. 可以设置一些session信息
  82.         StpUtil.getSession().set("userInfo", user);
  83.         
  84.         return ApiResult.success("登录成功", vo);
  85.     }
  86.    
  87.     @PostMapping("/logout")
  88.     public ApiResult logout() {
  89.         // 注销当前会话
  90.         StpUtil.logout();
  91.         return ApiResult.success("注销成功");
  92.     }
  93.    
  94.     @GetMapping("/info")
  95.     public ApiResult getUserInfo() {
  96.         // 获取当前登录用户ID
  97.         Object loginId = StpUtil.getLoginId();
  98.         
  99.         // 获取用户信息
  100.         User user = userService.findById(Long.parseLong(loginId.toString()));
  101.         
  102.         // 获取用户权限列表
  103.         List<String> permissionList = StpUtil.getPermissionList();
  104.         
  105.         // 获取用户角色列表
  106.         List<String> roleList = StpUtil.getRoleList();
  107.         
  108.         UserInfoVO vo = new UserInfoVO();
  109.         vo.setUser(user);
  110.         vo.setPermissions(permissionList);
  111.         vo.setRoles(roleList);
  112.         
  113.         return ApiResult.success(vo);
  114.     }
  115. }
  116. // 5. 业务Controller中使用
  117. @RestController
  118. @RequestMapping("/api/products")
  119. public class ProductController {
  120.    
  121.     @GetMapping("/list")
  122.     public ApiResult getProductList() {
  123.         // 无需手动检查登录状态,拦截器已处理
  124.         
  125.         // 获取当前登录用户ID
  126.         long userId = StpUtil.getLoginIdAsLong();
  127.         
  128.         List<Product> products = productService.getProductsByOwner(userId);
  129.         return ApiResult.success(products);
  130.     }
  131.    
  132.     @PostMapping("/create")
  133.     public ApiResult createProduct(@RequestBody ProductDTO dto) {
  134.         // 使用注解方式检查权限
  135.         // @SaCheckPermission("product.create") 也可以这样用
  136.         
  137.         // 编程式检查权限
  138.         StpUtil.checkPermission("product.create");
  139.         
  140.         long userId = StpUtil.getLoginIdAsLong();
  141.         dto.setOwnerId(userId);
  142.         
  143.         Product product = productService.createProduct(dto);
  144.         return ApiResult.success(product);
  145.     }
  146.    
  147.     @DeleteMapping("/{id}")
  148.     @SaCheckPermission("product.delete")  // 注解方式权限检查
  149.     public ApiResult deleteProduct(@PathVariable Long id) {
  150.         // 除了权限检查,还可以检查数据权限
  151.         Product product = productService.getById(id);
  152.         long currentUserId = StpUtil.getLoginIdAsLong();
  153.         
  154.         if (product.getOwnerId() != currentUserId) {
  155.             // 不是自己的商品,检查是否有管理员权限
  156.             StpUtil.checkRole("admin");
  157.         }
  158.         
  159.         productService.deleteProduct(id);
  160.         return ApiResult.success("删除成功");
  161.     }
  162.    
  163.     @GetMapping("/admin/dashboard")
  164.     @SaCheckRole("admin")  // 注解方式角色检查
  165.     public ApiResult getAdminDashboard() {
  166.         DashboardVO dashboard = dashboardService.getAdminDashboard();
  167.         return ApiResult.success(dashboard);
  168.     }
  169. }
  170. // 6. 进阶功能:踢人下线、账号封禁
  171. @Service
  172. public class AdvancedSecurityService {
  173.    
  174.     // 强制注销(踢人下线)
  175.     public void forceLogout(Object loginId) {
  176.         StpUtil.logout(loginId);
  177.     }
  178.    
  179.     // 封禁账号
  180.     public void disableAccount(Object loginId, long disableTime) {
  181.         // 封禁指定时间(单位:秒)
  182.         StpUtil.disable(loginId, disableTime);
  183.     }
  184.    
  185.     // 检查是否被封禁
  186.     public boolean isDisabled(Object loginId) {
  187.         return StpUtil.isDisable(loginId);
  188.     }
  189.    
  190.     // 二级认证(敏感操作需要再次验证)
  191.     public boolean startSecondAuth(long ttl) {
  192.         // 开启二级认证,有效期为ttl秒
  193.         return StpUtil.openSafe(ttl);
  194.     }
  195.    
  196.     // 检查二级认证
  197.     public void checkSecondAuth() {
  198.         StpUtil.checkSafe();
  199.     }
  200. }
复制代码
Sa-Token的优势与痛点

优势:

  • API设计极其简洁:StpUtil.xxx() 几乎涵盖了所有操作
  • 开箱即用:几乎零配置就能使用
  • 功能丰富:除了基础认证授权,还提供踢人下线、账号封禁、二级认证等高级功能
  • 国产框架:中文文档完善,符合国人使用习惯
  • 轻量级:依赖少,启动快
痛点:

  • 相对较新:生态不如Spring Security和Shiro成熟
  • 社区规模小:遇到复杂问题可能难以找到解决方案
  • 企业级特性有限:对OAuth2、LDAP等支持较弱
  • 过度封装:某些场景下灵活性不足
适用场景:

  • 中小型项目,追求开发效率
  • 团队对Spring Security/Shiro不熟悉
  • 需要快速搭建权限系统的原型或内部工具
  • 偏好国产框架和中文文档的团队
有些小伙伴第一次用Sa-Token时,会被它的简洁惊艳到。几行代码就实现了其他框架需要大量配置的功能。
但对于大型复杂系统,可能需要仔细评估它的扩展性和长期维护性。
5 三大框架全方位对比

了解了每个框架的单独特点后,我们来一个全方位的对比:
5.png

5.1 详细对比表

维度Spring SecurityApache ShiroSa-Token学习曲线陡峭 ⭐⭐⭐⭐⭐中等 ⭐⭐⭐☆☆平缓 ⭐⭐☆☆☆配置复杂度复杂 ⭐⭐⭐⭐⭐中等 ⭐⭐⭐☆☆简单 ⭐☆☆☆☆功能完整性全面 ⭐⭐⭐⭐⭐完整 ⭐⭐⭐⭐☆丰富 ⭐⭐⭐☆☆Spring生态集成原生 ⭐⭐⭐⭐⭐良好 ⭐⭐⭐☆☆良好 ⭐⭐⭐☆☆性能开销较高 ⭐⭐⭐☆☆中等 ⭐⭐⭐☆☆较低 ⭐⭐☆☆☆社区活跃度活跃 ⭐⭐⭐⭐⭐一般 ⭐⭐⭐☆☆增长 ⭐⭐⭐☆☆文档质量优秀(英文)⭐⭐⭐⭐⭐良好 ⭐⭐⭐☆☆优秀(中文)⭐⭐⭐⭐⭐扩展性强大 ⭐⭐⭐⭐⭐良好 ⭐⭐⭐☆☆一般 ⭐⭐⭐☆☆企业级特性丰富 ⭐⭐⭐⭐⭐有限 ⭐⭐☆☆☆有限 ⭐⭐☆☆☆5.2 技术特性详细对比

特性Spring SecurityApache ShiroSa-Token认证方式表单、Basic、OAuth2、LDAP、SAML等表单、Basic、CAS等表单、自定义授权模型RBAC、ABAC、方法级、URL级RBAC、URL级、方法级RBAC、方法级会话管理支持,与Spring Session集成强大,自带会话管理支持,简单易用密码加密多种加密方式支持多种加密方式支持支持缓存支持需要自行集成Spring Cache内置缓存支持支持Redis等单点登录通过Spring Security OAuth2需要额外模块需要额外模块微服务支持优秀,与Spring Cloud GateWay集成一般支持监控管理与Spring Boot Actuator集成需要自行实现简单监控6 如何做出最佳选择?

面对三个各有优劣的框架,如何做出最适合自己项目的选择?
我总结了一个决策流程图,帮助你在不同场景下做出明智决策:
6.png

6.1 具体场景建议

场景一:大型电商平台(选择Spring Security)


  • 理由:需要完善的OAuth2社交登录、支付安全、风控系统
  • 实施要点

    • 使用Spring Security OAuth2 Client集成第三方登录
    • 自定义安全过滤器实现风控逻辑
    • 与Spring Cloud Gateway整合实现统一认证
    • 使用Method Security注解实现细粒度权限控制

场景二:企业内部管理系统(选择Apache Shiro)


  • 理由:权限模型相对固定,需要快速开发,团队熟悉Shiro
  • 实施要点

    • 使用INI配置文件快速定义URL权限规则
    • 集成Ehcache缓存权限数据提升性能
    • 自定义Realm连接企业LDAP/AD域
    • 利用Shiro标签在页面上控制元素显示

场景三:创业公司MVP产品(选择Sa-Token)


  • 理由:需要快速上线验证想法,团队规模小,追求开发效率
  • 实施要点

    • 利用Sa-Token的零配置特性快速搭建
    • 使用注解方式实现基本权限控制
    • 集成Redis实现分布式会话
    • 利用Sa-Token的踢人功能实现基础管理

场景四:微服务架构系统(混合方案)


  • 理由:不同服务有不同的安全需求
  • 实施要点

    • 网关层:Spring Security + OAuth2(统一认证)
    • 核心业务服务:Spring Security(细粒度控制)
    • 内部管理服务:Apache Shiro(简单权限)
    • 工具类微服务:Sa-Token(快速开发)

6.2 如果选错了怎么办?

有些小伙伴可能会遇到这样的情况:项目初期选型不合适,随着业务发展需要迁移到其他框架。
这里提供一些迁移建议:

  • 渐进式迁移:新旧框架并行,逐步替换
  • 抽象隔离层:创建统一的安全接口,底层实现可替换
  • 分模块迁移:按业务模块逐个迁移,降低风险
  • 充分测试:特别是边缘案例和权限组合场景
  1. // 抽象安全接口示例
  2. public interface SecurityService {
  3.     // 认证相关
  4.     boolean login(String username, String password);
  5.     void logout();
  6.     boolean isAuthenticated();
  7.    
  8.     // 授权相关
  9.     boolean hasPermission(String permission);
  10.     boolean hasRole(String role);
  11.    
  12.     // 用户信息
  13.     Object getCurrentUser();
  14.     Long getCurrentUserId();
  15. }
  16. // Spring Security实现
  17. @Service
  18. public class SpringSecurityServiceImpl implements SecurityService {
  19.     // 实现基于Spring Security的接口
  20. }
  21. // 需要迁移时,只需实现新的实现类
  22. @Service  
  23. public class SaTokenServiceImpl implements SecurityService {
  24.     // 实现基于Sa-Token的接口
  25.     // 业务代码无需修改,只需切换实现
  26. }
复制代码
总结

经过深入分析,我们可以得出以下结论:

  • Spring Security企业级重型武器,功能全面但复杂,适合大型项目和有经验的团队。
  • Apache Shiro灵活实用的轻骑兵,平衡了功能与复杂度,适合大多数中小型项目。
  • Sa-Token快速开发的利器,API简洁但生态相对年轻,适合追求开发效率的场景。
实际上,没有完美的框架,只有合适的框架
最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
更多项目实战在我的技术网站:http://www.susan.net.cn/project

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册