SpringSecurity、Shiro?和?Sa-Token怎么選,哪個更好
前言
今天我們來聊聊一個讓很多Java開發(fā)者糾結(jié)的技術(shù)選型問題:Spring Security、Apache Shiro和Sa-Token,這3個主流安全框架到底該選哪個?
有些小伙伴在工作中可能遇到過這樣的場景:新項(xiàng)目啟動會上,架構(gòu)師堅(jiān)持要用Spring Security,團(tuán)隊(duì)里的老將卻說Shiro更簡單實(shí)用,而年輕的同事則力薦Sa-Token這個后起之秀。
大家各執(zhí)一詞,都有道理,到底該聽誰的?
今天這篇文章就跟大家一起聊聊這個話題,希望對你會有所幫助。
1 我們?yōu)槭裁葱枰踩蚣埽?/h2>
在深入對比之前,我們先要理解:為什么不能自己手寫安全邏輯,而非要用框架?
想象一下,如果你要為一個電商系統(tǒng)實(shí)現(xiàn)權(quán)限控制,你需要處理:
// 手寫權(quán)限控制的典型痛點(diǎn)
public class ManualSecurityExample {
// 1. 每個方法都要寫重復(fù)的權(quán)限校驗(yàn)
public void updateProduct(Long productId, ProductDTO dto) {
// 檢查用戶是否登錄
User user = getCurrentUser();
if (user == null) {
throw new UnauthorizedException("請先登錄");
}
// 檢查用戶是否有編輯權(quán)限
if (!user.hasPermission("product:update")) {
throw new ForbiddenException("沒有操作權(quán)限");
}
// 檢查是否是自己的商品(數(shù)據(jù)級權(quán)限)
Product product = productService.getById(productId);
if (!product.getOwnerId().equals(user.getId())) {
throw new ForbiddenException("只能修改自己的商品");
}
// 實(shí)際業(yè)務(wù)邏輯...
productService.update(productId, dto);
}
// 2. 每個Controller都要寫登錄檢查
// 3. 需要自己管理Session/Token
// 4. 密碼加密、CSRF防護(hù)都要自己實(shí)現(xiàn)
// 5. 審計(jì)日志、安全事件處理...
}看到問題了嗎?
安全邏輯會像“幽靈代碼”一樣滲透到業(yè)務(wù)的每個角落,導(dǎo)致:
- 代碼重復(fù)率高
- 業(yè)務(wù)邏輯和安全邏輯耦合
- 難以統(tǒng)一維護(hù)和升級
- 容易遺漏安全防護(hù)點(diǎn)
安全框架的價(jià)值,就是把這些問題抽象化、標(biāo)準(zhǔn)化、自動化。
下面這個示意圖展示了安全框架如何將安全關(guān)注點(diǎn)從業(yè)務(wù)代碼中解耦出來:

理解了安全框架的價(jià)值,接下來我們深入分析這三個主流選項(xiàng)。
2 Spring Security:企業(yè)級的安全“瑞士軍刀”
2.1 Spring Security是什么?
Spring Security是Spring官方提供的安全框架,可以說是Spring生態(tài)中的“御林軍”。
它不僅僅是一個權(quán)限控制框架,更是一個全面的安全解決方案。
2.2 核心架構(gòu):過濾器鏈的極致運(yùn)用
Spring Security的核心是過濾器鏈(Filter Chain)。
當(dāng)一個請求到達(dá)時(shí),它會經(jīng)過一系列安全過濾器,每個過濾器負(fù)責(zé)特定的安全功能:

2.3 快速搭建一個安全的REST API
// 1. 基礎(chǔ)配置類
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 禁用CSRF(REST API通常不需要)
.csrf().disable()
// 授權(quán)配置
.authorizeRequests()
.antMatchers("/api/public/**").permitAll() // 公開接口
.antMatchers("/api/admin/**").hasRole("ADMIN") // 需要管理員角色
.antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN") // 需要用戶角色
.anyRequest().authenticated() // 其他所有請求需要認(rèn)證
// 表單登錄配置(前后端分離時(shí)通常用不上)
.and()
.formLogin().disable()
// 基礎(chǔ)認(rèn)證配置
.httpBasic()
// 異常處理
.and()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint()) // 未認(rèn)證處理
.accessDeniedHandler(restAccessDeniedHandler()); // 權(quán)限不足處理
}
// 2. 用戶詳情服務(wù)(從數(shù)據(jù)庫加載用戶)
@Bean
public UserDetailsService userDetailsService() {
return username -> {
// 這里實(shí)際應(yīng)該查詢數(shù)據(jù)庫
if ("admin".equals(username)) {
return User.withUsername("admin")
.password(passwordEncoder().encode("admin123"))
.roles("ADMIN")
.build();
} else if ("user".equals(username)) {
return User.withUsername("user")
.password(passwordEncoder().encode("user123"))
.roles("USER")
.build();
}
throw new UsernameNotFoundException("用戶不存在: " + username);
};
}
// 3. 密碼編碼器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 4. REST API認(rèn)證入口點(diǎn)
@Bean
public AuthenticationEntryPoint restAuthenticationEntryPoint() {
return (request, response, authException) -> {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write(
"{\"code\": 401, \"message\": \"未認(rèn)證,請先登錄\"}"
);
};
}
}
// 5. 在Controller中使用安全注解
@RestController
@RequestMapping("/api")
public class ProductController {
@GetMapping("/public/products")
public List<Product> getPublicProducts() {
// 公開接口,無需認(rèn)證
return productService.getAllProducts();
}
@GetMapping("/user/products")
@PreAuthorize("hasRole('USER')") // 需要USER角色
public List<Product> getUserProducts() {
// 獲取當(dāng)前認(rèn)證用戶
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
return productService.getProductsByOwner(username);
}
@PostMapping("/admin/products")
@PreAuthorize("hasRole('ADMIN')") // 需要ADMIN角色
public Product createProduct(@RequestBody ProductDTO dto) {
return productService.createProduct(dto);
}
@DeleteMapping("/admin/products/{id}")
@PreAuthorize("hasPermission(#id, 'product', 'delete')") // 方法級權(quán)限控制
public void deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
}
}Spring Security的優(yōu)勢與痛點(diǎn)
優(yōu)勢:
- Spring生態(tài)原生支持:與Spring Boot、Spring Cloud無縫集成
- 功能全面:認(rèn)證、授權(quán)、防護(hù)(CSRF、CORS、點(diǎn)擊劫持等)一應(yīng)俱全
- 高度可定制:幾乎每個組件都可以自定義或替換
- 社區(qū)強(qiáng)大:Spring官方維護(hù),文檔完善,社區(qū)活躍
- 企業(yè)級特性:OAuth2、SAML、LDAP等企業(yè)級集成支持
痛點(diǎn):
- 學(xué)習(xí)曲線陡峭:概念復(fù)雜,配置繁瑣
- 過度設(shè)計(jì)感:簡單需求也需要復(fù)雜配置
- 調(diào)試?yán)щy:過濾器鏈復(fù)雜,問題定位困難
- 性能開銷:完整的過濾器鏈帶來一定性能損失
適用場景:
- 大型企業(yè)級應(yīng)用
- 需要與Spring生態(tài)深度集成的項(xiàng)目
- 需要OAuth2、LDAP等企業(yè)級認(rèn)證協(xié)議的項(xiàng)目
- 團(tuán)隊(duì)有Spring Security經(jīng)驗(yàn)的場景
有些小伙伴剛開始學(xué)Spring Security時(shí),可能會被它復(fù)雜的概念搞暈,比如:SecurityContext、Authentication、UserDetails、GrantedAuthority等等。
但一旦掌握了它的設(shè)計(jì)哲學(xué),你會發(fā)現(xiàn)它真的很強(qiáng)大。
3 Apache Shiro:簡單直觀的“輕騎兵”
3.1 Shiro是什么?
Apache Shiro是一個功能強(qiáng)大且易于使用的Java安全框架,它的設(shè)計(jì)哲學(xué)是:簡化應(yīng)用安全,讓安全變得更簡單。
如果說Spring Security是重型坦克,那么Shiro就是靈活機(jī)動的輕騎兵。
3.2 核心架構(gòu):四大核心概念
Shiro的架構(gòu)圍繞四個核心概念構(gòu)建:

3.3 快速實(shí)現(xiàn)基于URL的權(quán)限控制
// 1. Shiro配置類
@Configuration
public class ShiroConfig {
// 創(chuàng)建ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
// 設(shè)置登錄頁面
factoryBean.setLoginUrl("/login");
// 設(shè)置未授權(quán)頁面
factoryBean.setUnauthorizedUrl("/unauthorized");
// 配置攔截規(guī)則
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 靜態(tài)資源放行
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
// 公開接口
filterChainDefinitionMap.put("/api/public/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 需要認(rèn)證的接口
filterChainDefinitionMap.put("/api/user/**", "authc");
filterChainDefinitionMap.put("/api/admin/**", "authc, roles[admin]");
// 需要特定權(quán)限的接口
filterChainDefinitionMap.put("/api/products/create", "authc, perms[product:create]");
filterChainDefinitionMap.put("/api/products/delete/*", "authc, perms[product:delete]");
// 其他所有請求需要認(rèn)證
filterChainDefinitionMap.put("/**", "authc");
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean;
}
// 創(chuàng)建SecurityManager
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設(shè)置Realm
securityManager.setRealm(customRealm());
// 設(shè)置Session管理器
securityManager.setSessionManager(sessionManager());
// 設(shè)置緩存管理器
securityManager.setCacheManager(cacheManager());
return securityManager;
}
// 自定義Realm(連接安全數(shù)據(jù)源)
@Bean
public Realm customRealm() {
CustomRealm realm = new CustomRealm();
// 設(shè)置密碼匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("SHA-256");
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(false);
realm.setCredentialsMatcher(credentialsMatcher);
// 開啟緩存
realm.setCachingEnabled(true);
realm.setAuthenticationCachingEnabled(true);
realm.setAuthenticationCacheName("authenticationCache");
realm.setAuthorizationCachingEnabled(true);
realm.setAuthorizationCacheName("authorizationCache");
return realm;
}
}
// 2. 自定義Realm實(shí)現(xiàn)
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
// 認(rèn)證邏輯:驗(yàn)證用戶身份
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
// 從數(shù)據(jù)庫查詢用戶
User user = userService.findByUsername(username);
if (user == null) {
throw new UnknownAccountException("用戶不存在");
}
if (!user.isEnabled()) {
throw new DisabledAccountException("用戶已被禁用");
}
// 返回認(rèn)證信息
return new SimpleAuthenticationInfo(
user, // 身份 principal
user.getPassword(), // 憑證 credentials
getName() // realm name
);
}
// 授權(quán)邏輯:獲取用戶的角色和權(quán)限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 添加角色
Set<String> roles = userService.findRolesByUserId(user.getId());
authorizationInfo.setRoles(roles);
// 添加權(quán)限
Set<String> permissions = userService.findPermissionsByUserId(user.getId());
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
}
// 3. 在Controller中使用Shiro
@RestController
@RequestMapping("/api")
public class ProductController {
@GetMapping("/products")
public List<Product> getProducts() {
// 獲取當(dāng)前用戶
Subject currentUser = SecurityUtils.getSubject();
// 檢查是否已認(rèn)證
if (!currentUser.isAuthenticated()) {
throw new UnauthorizedException("請先登錄");
}
// 檢查是否有權(quán)限
if (!currentUser.isPermitted("product:view")) {
throw new ForbiddenException("沒有查看權(quán)限");
}
// 執(zhí)行業(yè)務(wù)邏輯
return productService.getAllProducts();
}
@PostMapping("/products")
public Product createProduct(@RequestBody ProductDTO dto) {
Subject currentUser = SecurityUtils.getSubject();
// 使用Shiro的權(quán)限注解(需要AOP支持)
currentUser.checkPermission("product:create");
// 或者使用編程式檢查
// if (!currentUser.isPermitted("product:create")) {
// throw new ForbiddenException("沒有創(chuàng)建權(quán)限");
// }
return productService.createProduct(dto);
}
@GetMapping("/admin/dashboard")
public DashboardVO getAdminDashboard() {
Subject currentUser = SecurityUtils.getSubject();
// 檢查是否具有admin角色
currentUser.checkRole("admin");
return dashboardService.getAdminDashboard();
}
}Shiro的優(yōu)勢與痛點(diǎn)
優(yōu)勢:
- 簡單直觀:API設(shè)計(jì)簡潔,學(xué)習(xí)成本低
- 配置靈活:支持INI、XML、注解等多種配置方式
- 功能完整:認(rèn)證、授權(quán)、會話管理、加密、緩存等一應(yīng)俱全
- 不依賴容器:可以在任何Java環(huán)境中運(yùn)行
- 易于集成:與Spring、Spring Boot等框架集成簡單
痛點(diǎn):
- Spring生態(tài)整合不夠原生:需要額外配置
- 社區(qū)活躍度下降:相比Spring Security,社區(qū)維護(hù)力度減弱
- 功能擴(kuò)展性有限:某些高級功能需要自己實(shí)現(xiàn)
- 文檔相對陳舊:部分文檔更新不及時(shí)
適用場景:
- 中小型項(xiàng)目,追求快速開發(fā)
- 非Spring項(xiàng)目或Spring生態(tài)不重的項(xiàng)目
- 團(tuán)隊(duì)對Spring Security不熟悉
- 需要簡單權(quán)限控制的內(nèi)部系統(tǒng)
有些小伙伴喜歡Shiro的簡潔,特別是它的INI配置文件,幾行配置就能搞定基本的權(quán)限控制。
但當(dāng)你需要更復(fù)雜的功能時(shí),可能會發(fā)現(xiàn)需要自己寫不少代碼。
4 Sa-Token:國產(chǎn)新星的“后起之秀”
4.1 Sa-Token是什么?
Sa-Token是一個輕量級Java權(quán)限認(rèn)證框架,由國內(nèi)開發(fā)者開發(fā)。
它的設(shè)計(jì)理念是:以最少的配置,完成最全面的權(quán)限認(rèn)證功能。
在Spring Security和Shiro之外,Sa-Token提供了一種新的選擇。
4.2 核心特性:簡單而強(qiáng)大
Sa-Token的核心設(shè)計(jì)哲學(xué)可以概括為:“簡單、強(qiáng)大、靈活”。
它通過幾個核心組件實(shí)現(xiàn)了完整的安全控制:

4.3 5分鐘搭建完整權(quán)限系統(tǒng)
// 1. 添加依賴(pom.xml)
// <dependency>
// <groupId>cn.dev33</groupId>
// <artifactId>sa-token-spring-boot-starter</artifactId>
// <version>1.34.0</version>
// </dependency>
// 2. 配置文件(application.yml)
// sa-token:
// token-name: satoken # token名稱
// timeout: 2592000 # token有效期,單位秒,默認(rèn)30天
// active-timeout: -1 # token活躍有效期,-1代表不限制
// is-concurrent: true # 是否允許并發(fā)登錄
// is-share: true # 在多人登錄同一賬號時(shí),是否共享token
// max-login-count: 12 # 同一賬號最大登錄數(shù)量
// is-write-header: true # 是否將token寫入響應(yīng)頭
// token-style: uuid # token風(fēng)格
// is-log: false # 是否打印操作日志
// 3. 配置類(可選)
@Configuration
public class SaTokenConfig {
// 注冊攔截器
@Bean
public SaInterceptor saInterceptor() {
return new SaInterceptor()
// 校驗(yàn)登錄狀態(tài),不包含登錄接口
.addPathPatterns("/**")
.excludePathPatterns("/api/user/login")
.excludePathPatterns("/api/public/**")
// 權(quán)限校驗(yàn)規(guī)則
.check(r -> {
// 1. 檢查登錄狀態(tài)
SaRouter.match("/api/**", () -> {
StpUtil.checkLogin();
});
// 2. 角色校驗(yàn)
SaRouter.match("/api/admin/**", () -> {
StpUtil.checkRole("admin");
});
// 3. 權(quán)限校驗(yàn)
SaRouter.match("/api/products/create", () -> {
StpUtil.checkPermission("product.create");
});
SaRouter.match("/api/products/delete/**", () -> {
StpUtil.checkPermission("product.delete");
});
});
}
}
// 4. 登錄認(rèn)證Controller
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/login")
public ApiResult login(@RequestBody LoginDTO dto) {
// 1. 驗(yàn)證用戶名密碼
User user = userService.findByUsername(dto.getUsername());
if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
return ApiResult.error("用戶名或密碼錯誤");
}
// 2. 登錄(Sa-Token會自動創(chuàng)建token)
StpUtil.login(user.getId());
// 3. 獲取token信息
String tokenValue = StpUtil.getTokenValue();
long tokenTimeout = StpUtil.getTokenTimeout();
// 4. 返回用戶信息和token
LoginVO vo = new LoginVO();
vo.setUserId(user.getId());
vo.setUsername(user.getUsername());
vo.setToken(tokenValue);
vo.setExpireTime(tokenTimeout);
// 5. 可以設(shè)置一些session信息
StpUtil.getSession().set("userInfo", user);
return ApiResult.success("登錄成功", vo);
}
@PostMapping("/logout")
public ApiResult logout() {
// 注銷當(dāng)前會話
StpUtil.logout();
return ApiResult.success("注銷成功");
}
@GetMapping("/info")
public ApiResult getUserInfo() {
// 獲取當(dāng)前登錄用戶ID
Object loginId = StpUtil.getLoginId();
// 獲取用戶信息
User user = userService.findById(Long.parseLong(loginId.toString()));
// 獲取用戶權(quán)限列表
List<String> permissionList = StpUtil.getPermissionList();
// 獲取用戶角色列表
List<String> roleList = StpUtil.getRoleList();
UserInfoVO vo = new UserInfoVO();
vo.setUser(user);
vo.setPermissions(permissionList);
vo.setRoles(roleList);
return ApiResult.success(vo);
}
}
// 5. 業(yè)務(wù)Controller中使用
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/list")
public ApiResult getProductList() {
// 無需手動檢查登錄狀態(tài),攔截器已處理
// 獲取當(dāng)前登錄用戶ID
long userId = StpUtil.getLoginIdAsLong();
List<Product> products = productService.getProductsByOwner(userId);
return ApiResult.success(products);
}
@PostMapping("/create")
public ApiResult createProduct(@RequestBody ProductDTO dto) {
// 使用注解方式檢查權(quán)限
// @SaCheckPermission("product.create") 也可以這樣用
// 編程式檢查權(quán)限
StpUtil.checkPermission("product.create");
long userId = StpUtil.getLoginIdAsLong();
dto.setOwnerId(userId);
Product product = productService.createProduct(dto);
return ApiResult.success(product);
}
@DeleteMapping("/{id}")
@SaCheckPermission("product.delete") // 注解方式權(quán)限檢查
public ApiResult deleteProduct(@PathVariable Long id) {
// 除了權(quán)限檢查,還可以檢查數(shù)據(jù)權(quán)限
Product product = productService.getById(id);
long currentUserId = StpUtil.getLoginIdAsLong();
if (product.getOwnerId() != currentUserId) {
// 不是自己的商品,檢查是否有管理員權(quán)限
StpUtil.checkRole("admin");
}
productService.deleteProduct(id);
return ApiResult.success("刪除成功");
}
@GetMapping("/admin/dashboard")
@SaCheckRole("admin") // 注解方式角色檢查
public ApiResult getAdminDashboard() {
DashboardVO dashboard = dashboardService.getAdminDashboard();
return ApiResult.success(dashboard);
}
}
// 6. 進(jìn)階功能:踢人下線、賬號封禁
@Service
public class AdvancedSecurityService {
// 強(qiáng)制注銷(踢人下線)
public void forceLogout(Object loginId) {
StpUtil.logout(loginId);
}
// 封禁賬號
public void disableAccount(Object loginId, long disableTime) {
// 封禁指定時(shí)間(單位:秒)
StpUtil.disable(loginId, disableTime);
}
// 檢查是否被封禁
public boolean isDisabled(Object loginId) {
return StpUtil.isDisable(loginId);
}
// 二級認(rèn)證(敏感操作需要再次驗(yàn)證)
public boolean startSecondAuth(long ttl) {
// 開啟二級認(rèn)證,有效期為ttl秒
return StpUtil.openSafe(ttl);
}
// 檢查二級認(rèn)證
public void checkSecondAuth() {
StpUtil.checkSafe();
}
}Sa-Token的優(yōu)勢與痛點(diǎn)
優(yōu)勢:
- API設(shè)計(jì)極其簡潔:
StpUtil.xxx()幾乎涵蓋了所有操作 - 開箱即用:幾乎零配置就能使用
- 功能豐富:除了基礎(chǔ)認(rèn)證授權(quán),還提供踢人下線、賬號封禁、二級認(rèn)證等高級功能
- 國產(chǎn)框架:中文文檔完善,符合國人使用習(xí)慣
- 輕量級:依賴少,啟動快
痛點(diǎn):
- 相對較新:生態(tài)不如Spring Security和Shiro成熟
- 社區(qū)規(guī)模小:遇到復(fù)雜問題可能難以找到解決方案
- 企業(yè)級特性有限:對OAuth2、LDAP等支持較弱
- 過度封裝:某些場景下靈活性不足
適用場景:
- 中小型項(xiàng)目,追求開發(fā)效率
- 團(tuán)隊(duì)對Spring Security/Shiro不熟悉
- 需要快速搭建權(quán)限系統(tǒng)的原型或內(nèi)部工具
- 偏好國產(chǎn)框架和中文文檔的團(tuán)隊(duì)
有些小伙伴第一次用Sa-Token時(shí),會被它的簡潔驚艷到。幾行代碼就實(shí)現(xiàn)了其他框架需要大量配置的功能。
但對于大型復(fù)雜系統(tǒng),可能需要仔細(xì)評估它的擴(kuò)展性和長期維護(hù)性。
5 三大框架全方位對比
了解了每個框架的單獨(dú)特點(diǎn)后,我們來一個全方位的對比:

5.1 詳細(xì)對比表
| 維度 | Spring Security | Apache Shiro | Sa-Token |
|---|---|---|---|
| 學(xué)習(xí)曲線 | 陡峭 ????? | 中等 ???☆☆ | 平緩 ??☆☆☆ |
| 配置復(fù)雜度 | 復(fù)雜 ????? | 中等 ???☆☆ | 簡單 ?☆☆☆☆ |
| 功能完整性 | 全面 ????? | 完整 ????☆ | 豐富 ???☆☆ |
| Spring生態(tài)集成 | 原生 ????? | 良好 ???☆☆ | 良好 ???☆☆ |
| 性能開銷 | 較高 ???☆☆ | 中等 ???☆☆ | 較低 ??☆☆☆ |
| 社區(qū)活躍度 | 活躍 ????? | 一般 ???☆☆ | 增長 ???☆☆ |
| 文檔質(zhì)量 | 優(yōu)秀(英文)????? | 良好 ???☆☆ | 優(yōu)秀(中文)????? |
| 擴(kuò)展性 | 強(qiáng)大 ????? | 良好 ???☆☆ | 一般 ???☆☆ |
| 企業(yè)級特性 | 豐富 ????? | 有限 ??☆☆☆ | 有限 ??☆☆☆ |
5.2 技術(shù)特性詳細(xì)對比
| 特性 | Spring Security | Apache Shiro | Sa-Token |
|---|---|---|---|
| 認(rèn)證方式 | 表單、Basic、OAuth2、LDAP、SAML等 | 表單、Basic、CAS等 | 表單、自定義 |
| 授權(quán)模型 | RBAC、ABAC、方法級、URL級 | RBAC、URL級、方法級 | RBAC、方法級 |
| 會話管理 | 支持,與Spring Session集成 | 強(qiáng)大,自帶會話管理 | 支持,簡單易用 |
| 密碼加密 | 多種加密方式支持 | 多種加密方式支持 | 支持 |
| 緩存支持 | 需要自行集成Spring Cache | 內(nèi)置緩存支持 | 支持Redis等 |
| 單點(diǎn)登錄 | 通過Spring Security OAuth2 | 需要額外模塊 | 需要額外模塊 |
| 微服務(wù)支持 | 優(yōu)秀,與Spring Cloud GateWay集成 | 一般 | 支持 |
| 監(jiān)控管理 | 與Spring Boot Actuator集成 | 需要自行實(shí)現(xiàn) | 簡單監(jiān)控 |
6 如何做出最佳選擇?
面對三個各有優(yōu)劣的框架,如何做出最適合自己項(xiàng)目的選擇?
我總結(jié)了一個決策流程圖,幫助你在不同場景下做出明智決策:

6.1 具體場景建議
場景一:大型電商平臺(選擇Spring Security)
- 理由:需要完善的OAuth2社交登錄、支付安全、風(fēng)控系統(tǒng)
- 實(shí)施要點(diǎn):
- 使用Spring Security OAuth2 Client集成第三方登錄
- 自定義安全過濾器實(shí)現(xiàn)風(fēng)控邏輯
- 與Spring Cloud Gateway整合實(shí)現(xiàn)統(tǒng)一認(rèn)證
- 使用Method Security注解實(shí)現(xiàn)細(xì)粒度權(quán)限控制
場景二:企業(yè)內(nèi)部管理系統(tǒng)(選擇Apache Shiro)
- 理由:權(quán)限模型相對固定,需要快速開發(fā),團(tuán)隊(duì)熟悉Shiro
- 實(shí)施要點(diǎn):
- 使用INI配置文件快速定義URL權(quán)限規(guī)則
- 集成Ehcache緩存權(quán)限數(shù)據(jù)提升性能
- 自定義Realm連接企業(yè)LDAP/AD域
- 利用Shiro標(biāo)簽在頁面上控制元素顯示
場景三:創(chuàng)業(yè)公司MVP產(chǎn)品(選擇Sa-Token)
- 理由:需要快速上線驗(yàn)證想法,團(tuán)隊(duì)規(guī)模小,追求開發(fā)效率
- 實(shí)施要點(diǎn):
- 利用Sa-Token的零配置特性快速搭建
- 使用注解方式實(shí)現(xiàn)基本權(quán)限控制
- 集成Redis實(shí)現(xiàn)分布式會話
- 利用Sa-Token的踢人功能實(shí)現(xiàn)基礎(chǔ)管理
場景四:微服務(wù)架構(gòu)系統(tǒng)(混合方案)
- 理由:不同服務(wù)有不同的安全需求
- 實(shí)施要點(diǎn):
- 網(wǎng)關(guān)層:Spring Security + OAuth2(統(tǒng)一認(rèn)證)
- 核心業(yè)務(wù)服務(wù):Spring Security(細(xì)粒度控制)
- 內(nèi)部管理服務(wù):Apache Shiro(簡單權(quán)限)
- 工具類微服務(wù):Sa-Token(快速開發(fā))
6.2 如果選錯了怎么辦?
有些小伙伴可能會遇到這樣的情況:項(xiàng)目初期選型不合適,隨著業(yè)務(wù)發(fā)展需要遷移到其他框架。
這里提供一些遷移建議:
- 漸進(jìn)式遷移:新舊框架并行,逐步替換
- 抽象隔離層:創(chuàng)建統(tǒng)一的安全接口,底層實(shí)現(xiàn)可替換
- 分模塊遷移:按業(yè)務(wù)模塊逐個遷移,降低風(fēng)險(xiǎn)
- 充分測試:特別是邊緣案例和權(quán)限組合場景
// 抽象安全接口示例
public interface SecurityService {
// 認(rèn)證相關(guān)
boolean login(String username, String password);
void logout();
boolean isAuthenticated();
// 授權(quán)相關(guān)
boolean hasPermission(String permission);
boolean hasRole(String role);
// 用戶信息
Object getCurrentUser();
Long getCurrentUserId();
}
// Spring Security實(shí)現(xiàn)
@Service
public class SpringSecurityServiceImpl implements SecurityService {
// 實(shí)現(xiàn)基于Spring Security的接口
}
// 需要遷移時(shí),只需實(shí)現(xiàn)新的實(shí)現(xiàn)類
@Service
public class SaTokenServiceImpl implements SecurityService {
// 實(shí)現(xiàn)基于Sa-Token的接口
// 業(yè)務(wù)代碼無需修改,只需切換實(shí)現(xiàn)
}總結(jié)
經(jīng)過深入分析,我們可以得出以下結(jié)論:
- Spring Security是企業(yè)級重型武器,功能全面但復(fù)雜,適合大型項(xiàng)目和有經(jīng)驗(yàn)的團(tuán)隊(duì)。
- Apache Shiro是靈活實(shí)用的輕騎兵,平衡了功能與復(fù)雜度,適合大多數(shù)中小型項(xiàng)目。
- Sa-Token是快速開發(fā)的利器,API簡潔但生態(tài)相對年輕,適合追求開發(fā)效率的場景。
實(shí)際上,沒有完美的框架,只有合適的框架。
到此這篇關(guān)于SpringSecurity、Shiro 和 Sa-Token,選哪個更好?的文章就介紹到這了,更多相關(guān)SpringSecurity、Shiro 和 Sa-Token,選哪個更好?內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 關(guān)于SpringSecurity簡介以及和Shiro的區(qū)別
- SpringSecurity框架簡介及與shiro特點(diǎn)對比
- Spring Security與SaToken的對比與優(yōu)缺點(diǎn)分析
- SpringSecurity6.0 如何通過JWTtoken進(jìn)行認(rèn)證授權(quán)
- SpringSecurity6.4中一次性令牌登錄(One-Time Token Login)實(shí)現(xiàn)
- SpringSecurity實(shí)現(xiàn)前后端分離登錄token認(rèn)證詳解
- springsecurity基于token的認(rèn)證方式
- SpringSecurity Jwt Token 自動刷新的實(shí)現(xiàn)
相關(guān)文章
IntelliJ IDEA中如何調(diào)試Java Stream操作
這篇文章主要介紹了IntelliJ IDEA中如何優(yōu)雅的調(diào)試Java Stream操作,在強(qiáng)大的IDEA插件支持下,stream的調(diào)試其實(shí)也沒那么難了,下面就來學(xué)習(xí)一下在IDEA中如何調(diào)試stream操作吧2022-05-05
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之前綴,中綴和后綴表達(dá)式
這篇文章主要為大家介紹了Java的前綴,中綴和后綴表達(dá)式 ,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
Java 多線程并發(fā)AbstractQueuedSynchronizer詳情
這篇文章主要介紹了Java 多線程并發(fā)AbstractQueuedSynchronizer詳情,文章圍繞主題展開想象的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-06-06
SpringBoot整合sharding-jdbc實(shí)現(xiàn)分庫分表與讀寫分離的示例
本文主要介紹了SpringBoot整合sharding-jdbc實(shí)現(xiàn)分庫分表與讀寫分離的示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

