Spring Security vs Shiro vs Sa‑Token: Which Java Security Framework Should You Choose?

This article compares three popular Java security frameworks—Spring Security, Apache Shiro, and Sa‑Token—by examining their architecture, advantages, drawbacks, typical use cases, and providing a detailed side‑by‑side comparison to help developers make an informed framework selection.

macrozheng
macrozheng
macrozheng
Spring Security vs Shiro vs Sa‑Token: Which Java Security Framework Should You Choose?

Why Use a Security Framework?

Manually coding authentication, authorization, CSRF protection, session handling and audit logging leads to duplicated code, tight coupling with business logic and a high risk of missing security checks. A dedicated framework abstracts and standardises these concerns, keeping security out of core business code.

Spring Security – Enterprise‑Grade "Swiss Army Knife"

Core Architecture

Requests flow through a configurable filter chain . Each filter performs a specific security function (e.g., authentication, CSRF, CORS, exception handling).

Quick Setup for a REST API

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated()
            .and()
                .formLogin().disable()
                .httpBasic()
            .and()
                .exceptionHandling()
                .authenticationEntryPoint(restAuthenticationEntryPoint())
                .accessDeniedHandler(restAccessDeniedHandler());
    }

    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            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("User not found: " + username);
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @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\":\"Unauthenticated, please log in\"}");
        };
    }
}

Advantages

Native integration with Spring Boot and Spring Cloud.

Comprehensive feature set: authentication, authorization, CSRF, CORS, click‑jacking protection, OAuth2, SAML, LDAP, etc.

Highly customisable – virtually every component can be overridden.

Large, active community and official documentation.

Pain Points

Steep learning curve; many concepts and configuration options.

Heavyweight for simple use‑cases.

Debugging can be difficult due to deep filter chain.

Additional performance overhead from the full filter chain.

Suitable Scenarios

Large‑scale enterprise applications.

Projects that require deep Spring ecosystem integration.

Systems needing enterprise‑grade protocols such as OAuth2 or LDAP.

Teams already experienced with Spring Security.

Apache Shiro – Simple, Agile "Light Cavalry"

Core Concepts

Shiro is built around four core concepts: Subject (current user), SecurityManager , Realm (data source) and Session . It supports authentication, authorization, session management, cryptography and caching.

Quick URL‑Based Permission Control

@Configuration
public class ShiroConfig {
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setUnauthorizedUrl("/unauthorized");
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/static/**", "anon");
        map.put("/api/public/**", "anon");
        map.put("/api/user/**", "authc");
        map.put("/api/admin/**", "authc, roles[admin]");
        map.put("/api/products/create", "authc, perms[product:create]");
        map.put("/api/products/delete/*", "authc, perms[product:delete]");
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(customRealm());
        manager.setSessionManager(sessionManager());
        manager.setCacheManager(cacheManager());
        return manager;
    }

    @Bean
    public Realm customRealm() {
        CustomRealm realm = new CustomRealm();
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("SHA-256");
        matcher.setHashIterations(1024);
        matcher.setStoredCredentialsHexEncoded(false);
        realm.setCredentialsMatcher(matcher);
        realm.setCachingEnabled(true);
        realm.setAuthenticationCachingEnabled(true);
        realm.setAuthorizationCachingEnabled(true);
        return realm;
    }
}

Advantages

Simple and intuitive API; low learning cost.

Flexible configuration (INI, XML, annotations).

Feature‑complete: authentication, authorization, session, encryption, caching.

Container‑agnostic – works in any Java environment.

Easy integration with Spring/Spring Boot.

Pain Points

Native Spring integration is not as seamless as Spring Security.

Community activity has declined compared with Spring Security.

Advanced enterprise features may require custom implementation.

Documentation can lag behind releases.

Suitable Scenarios

Small‑to‑medium projects that need rapid development.

Non‑Spring or lightweight Java applications.

Teams unfamiliar with Spring Security but comfortable with a simple framework.

Internal tools requiring straightforward permission control.

Sa‑Token – New Chinese "Rising Star"

Core Features

Sa‑Token provides a lightweight, zero‑configuration security solution. All operations are performed via the static StpUtil.xxx() API. It supports login/logout, role/permission checks, session handling, token management, forced logout, account disabling and second‑factor authentication.

Dependency (Maven)

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>

Basic Configuration (application.yml)

sa-token:
  token-name: satoken
  timeout: 2592000   # 30 days (seconds)
  active-timeout: -1
  is-concurrent: true
  is-share: true
  max-login-count: 12
  is-write-header: true
  token-style: uuid
  is-log: false

Interceptor (optional)

@Configuration
public class SaTokenConfig {
    @Bean
    public SaInterceptor saInterceptor() {
        return new SaInterceptor()
            .addPathPatterns("/**")
            .excludePathPatterns("/api/user/login", "/api/public/**")
            .check(r -> {
                SaRouter.match("/api/**", () -> StpUtil.checkLogin());
                SaRouter.match("/api/admin/**", () -> StpUtil.checkRole("admin"));
                SaRouter.match("/api/products/create", () -> StpUtil.checkPermission("product.create"));
                SaRouter.match("/api/products/delete/**", () -> StpUtil.checkPermission("product.delete"));
            });
    }
}

Login Controller Example

@RestController
@RequestMapping("/api/user")
public class UserController {
    @PostMapping("/login")
    public ApiResult login(@RequestBody LoginDTO dto) {
        User user = userService.findByUsername(dto.getUsername());
        if (user == null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
            return ApiResult.error("Invalid username or password");
        }
        StpUtil.login(user.getId());
        LoginVO vo = new LoginVO();
        vo.setUserId(user.getId());
        vo.setUsername(user.getUsername());
        vo.setToken(StpUtil.getTokenValue());
        vo.setExpireTime(StpUtil.getTokenTimeout());
        StpUtil.getSession().set("userInfo", user);
        return ApiResult.success("Login successful", vo);
    }

    @PostMapping("/logout")
    public ApiResult logout() {
        StpUtil.logout();
        return ApiResult.success("Logout successful");
    }

    @GetMapping("/info")
    public ApiResult getUserInfo() {
        Object loginId = StpUtil.getLoginId();
        User user = userService.findById(Long.parseLong(loginId.toString()));
        UserInfoVO vo = new UserInfoVO();
        vo.setUser(user);
        vo.setPermissions(StpUtil.getPermissionList());
        vo.setRoles(StpUtil.getRoleList());
        return ApiResult.success(vo);
    }
}

Business Controller Example

@RestController
@RequestMapping("/api/products")
public class ProductController {
    @GetMapping("/list")
    public ApiResult getProductList() {
        long userId = StpUtil.getLoginIdAsLong();
        List<Product> products = productService.getProductsByOwner(userId);
        return ApiResult.success(products);
    }

    @PostMapping("/create")
    public ApiResult createProduct(@RequestBody ProductDTO dto) {
        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")
    public ApiResult deleteProduct(@PathVariable Long id) {
        Product product = productService.getById(id);
        long currentUserId = StpUtil.getLoginIdAsLong();
        if (product.getOwnerId() != currentUserId) {
            StpUtil.checkRole("admin");
        }
        productService.deleteProduct(id);
        return ApiResult.success("Deleted successfully");
    }
}

Advanced Features

@Service
public class AdvancedSecurityService {
    public void forceLogout(Object loginId) { StpUtil.logout(loginId); }
    public void disableAccount(Object loginId, long disableTime) { StpUtil.disable(loginId, disableTime); }
    public boolean isDisabled(Object loginId) { return StpUtil.isDisable(loginId); }
    public boolean startSecondAuth(long ttl) { return StpUtil.openSafe(ttl); }
    public void checkSecondAuth() { StpUtil.checkSafe(); }
}

Advantages

Extremely simple API – all actions via StpUtil.xxx().

Zero‑configuration for most scenarios.

Rich feature set: login, logout, role/permission checks, session, token, kick‑out, account disabling, second‑factor auth.

Complete Chinese documentation.

Lightweight – minimal dependencies and fast startup.

Pain Points

Relatively new ecosystem; fewer third‑party resources.

Community is smaller than Spring Security or Shiro.

Enterprise‑grade features (OAuth2, LDAP, SSO) are less mature.

High level of abstraction can limit fine‑grained flexibility.

Suitable Scenarios

Small‑to‑medium projects that prioritize rapid development.

Teams unfamiliar with Spring Security or Shiro.

Prototypes or internal tools where speed outweighs extensive enterprise features.

Teams that prefer a Chinese‑native framework and documentation.

Full Comparison of the Three Frameworks

Dimension Overview

Dimension            | Spring Security | Apache Shiro | Sa-Token
----------------------|----------------|--------------|----------
Learning Curve        | ★★★★★ (steep) | ★★★☆☆ (moderate) | ★★☆☆☆ (gentle)
Configuration Complexity| ★★★★★ (complex) | ★★★☆☆ (moderate) | ★☆☆☆☆ (simple)
Feature Completeness | ★★★★★ (full)   | ★★★★☆ (complete) | ★★★☆☆ (rich)
Spring Integration    | ★★★★★ (native) | ★★★☆☆ (good) | ★★★☆☆ (good)
Performance Overhead  | ★★★☆☆ (higher) | ★★★☆☆ (moderate) | ★★☆☆☆ (low)
Community Activity    | ★★★★★ (active) | ★★★☆☆ (average) | ★★★☆☆ (growing)
Documentation Quality | ★★★★★ (excellent, English) | ★★★☆☆ (good) | ★★★★★ (excellent, Chinese)
Extensibility         | ★★★★★ (strong) | ★★★☆☆ (good) | ★★★☆☆ (average)
Enterprise Features   | ★★★★★ (rich)   | ★★☆☆☆ (limited) | ★★☆☆☆ (limited)

Technical Feature Matrix

Feature                | Spring Security                | Apache Shiro                | Sa-Token
----------------------|--------------------------------|----------------------------|--------------------------
Authentication Methods | Form, Basic, OAuth2, LDAP, SAML | Form, Basic, CAS           | Form, custom
Authorization Model   | RBAC, ABAC, method‑level, URL‑level | RBAC, URL‑level, method‑level | RBAC, method‑level
Session Management    | Spring Session integration      | Built‑in                   | Simple built‑in
Password Encryption    | Multiple algorithms supported   | Multiple algorithms supported | Supported
Cache Support          | Requires Spring Cache integration | Built‑in                 | Redis, etc.
Single Sign‑On (SSO)  | Spring Security OAuth2          | Extra module needed        | Extra module needed
Microservice Support  | Excellent (Spring Cloud Gateway) | General                    | Supported
Monitoring & Management| Spring Boot Actuator integration | Custom implementation needed | Simple monitoring

How to Choose the Best Framework?

Large enterprise systems with complex security requirements (OAuth2, SAML, LDAP, fine‑grained method security): Choose Spring Security and leverage its extensive ecosystem.

Medium‑size internal tools or non‑Spring projects that need quick, straightforward permission control: Choose Apache Shiro for its simplicity and flexible configuration.

Start‑ups or prototypes where development speed and minimal configuration are critical: Choose Sa‑Token for its zero‑configuration API.

Microservice architectures with heterogeneous needs: Combine frameworks – use Spring Security (or OAuth2) at the API gateway, Spring Security for core services, Shiro for lightweight internal tools, and Sa‑Token for fast‑developed utilities.

Migration Strategy When the Initial Choice Becomes Inadequate

Run old and new frameworks side‑by‑side, gradually replace components.

Introduce an abstraction layer (e.g., a unified SecurityService interface) so business code depends only on the abstraction.

Migrate module‑by‑module to limit risk.

Invest in comprehensive testing, especially for edge‑case permission combinations.

public interface SecurityService {
    // Authentication
    boolean login(String username, String password);
    void logout();
    boolean isAuthenticated();
    // Authorization
    boolean hasPermission(String permission);
    boolean hasRole(String role);
    // User info
    Object getCurrentUser();
    Long getCurrentUserId();
}

// Spring Security implementation
@Service
public class SpringSecurityServiceImpl implements SecurityService {
    // Implement using Spring Security APIs
}

// Sa‑Token implementation
@Service
public class SaTokenServiceImpl implements SecurityService {
    // Implement using StpUtil APIs
}

Conclusion

Spring Security – an enterprise‑grade heavyweight offering full‑featured security at the cost of complexity; ideal for large projects and experienced teams.

Apache Shiro – a flexible, lightweight solution that balances features and simplicity; suitable for most medium‑size applications.

Sa‑Token – a rapid‑development framework with a concise API and Chinese documentation; perfect for startups and scenarios where speed outweighs extensive enterprise features.

There is no universally perfect framework – the best choice aligns with project requirements, team expertise and long‑term maintenance considerations.

framework comparisonSpring SecurityApache ShiroSa-TokenJava Security
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.