Sa-Token Deep Dive: 10 Interview Questions on Auth, Multi-Account & SSO
This article explores 10 key Sa-Token interview questions covering core interfaces, one-line login internals, token styles, second-level authentication, multi-account architecture, distributed storage options, SSO comparisons, token passing strategies, permission annotation implementation, and production security hardening.
Interview Self-Test
Q1: What is Sa-Token's core interface? What design pattern does it embody? — Chapter: Concept Definition
Q2: What is the one-line API for Sa-Token login? What happens underneath? — Chapter: Quick Start
Q3: What are Sa-Token's three token styles? How do their use cases differ? — Chapter: Advanced Features
Q4: What problem does Sa-Token's second-level authentication (Sub-Login) solve? How to use it? — Chapter: Advanced Features
Q5: How does Sa-Token implement multi-account systems? How does it differ from single-account? — Chapter: Spring Boot Integration
Q6: How does Sa-Token support distributed deployment? What storage options exist? — Chapter: Common Pitfalls
Q7: What are the pros/cons of Sa-Token SSO vs OAuth2 SSO? How to choose? — Chapter: Comparative Analysis
Q8: In frontend-backend separation, how is Sa-Token's token passed? What to watch for? — Chapter: Scenario Discussion
Q9: How is Sa-Token's @SaCheckPermission annotation implemented under the hood? — Chapter: Source Code Analysis
Q10: What security hardening recommendations exist for Sa-Token in production? — Chapter: Best Practices
Q1: Sa-Token's Core Interface and Design Patterns
Answer: Sa-Token's core interface is StpLogic; all authentication and authorization operations go through this interface.
// StpLogic core interface (partial methods)
public interface StpLogic {
// Login
void login(Object id);
void login(Object id, boolean rememberMe);
// Logout
void logout();
void logout(Object loginId);
// Authentication check
void checkLogin();
// Permission check
void checkPermission(String permission);
void checkRole(String role);
// Token operations
String getTokenValue();
void setTokenValue(String tokenValue, int timeout);
// Session operations
SaSession getSession();
SaSession getSessionByLoginId(Object loginId);
// Online users
List<Object> getLoginIdList();
int getTokenSessionTimeout();
}Design Patterns:
Interface Segregation: Auth and authz separated — StpLogic defines core, StpUtil provides static calls
Strategy: Swappable storage implementations — Redis, Memory, InfluxDB, etc.
Singleton: Global unique StpLogic instance — StpLogic DEFAULT static instance
Adapter: Adapt to different frameworks — Sa-Token-Servlet adapter
// StpUtil is StpLogic's static utility class (Singleton)
public class StpUtil {
public static final StpLogic stpLogic = new StpLogic("/user");
public static void login(Object id) {
stpLogic.login(id);
}
public static void checkLogin() {
stpLogic.checkLogin();
}
}Reference: Sa-Token认证框架全解析 - Concept Definition
Q2: One-Line Login API and Underlying Flow
Answer: StpUtil.login(10001); // One line completes login
Underlying Execution Flow:
1. Generate Token (UUID or Snowflake ID)
token = UUID.randomUUID().toString()
2. Store Token → userId mapping
redis.set("satoken:login:login-token:" + token, "10001", timeout)
3. Store userId → Session mapping
redis.set("satoken:login:login-session:10001", sessionData)
4. Write Cookie (or return to frontend)
response.addCookie("satoken", token) // Simplified source code
public void login(Object loginId) {
// 1. Generate Token
String token = createToken(loginId);
// 2. Write to storage (Redis)
String loginTokenKey = SpConsts.LOGIN_TOKEN_SPILIT + token;
SaManager.getSaTokenStore().set(loginTokenKey, loginId, SaManager.getTokenTimeout);
// 3. Create Session
SaManager.getSaTokenStore().getSessionByLoginId(loginId);
// 4. Write Cookie
SaManager.getTokenTop().setToken(token, SaManager.getTokenTimeout);
}Reference: Sa-Token认证框架全解析 - Quick Start
Q3: Three Token Styles and Applicable Scenarios
Answer:
UUID: Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx — Pros: Globally unique, extremely low collision probability — Cons: Long length (36 chars) — Use Case: General purpose
Simple-UUID: Format: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx — Pros: Removes hyphens, shorter length — Cons: Same security level as UUID — Use Case: Mobile, URL parameters
Random-32: Format: Random 32-char string — Pros: Fixed length, aesthetic — Cons: Randomness depends on algorithm — Use Case: Default recommended
# Configure token style
sa-token:
token-style: random-32 # random-32 | uuid | simple-uuid // Custom token generator
SaTokenStrategy.stpLogic.setTokenGenerator(new TokenGenerator() {
@Override
public String generateToken(Object loginId, String loginType) {
// Snowflake algorithm generates unique ID
return SnowflakeIdGenerator.generateId();
}
});Reference: Sa-Token认证框架全解析 - Advanced Features
Q4: Second-Level Authentication (Sub-Login)
Problem Solved: On an already logged-in account, perform secondary authentication for specific functional areas, similar to Alipay's "payment password".
Scenario: User logged in, now wants to change password/view sensitive data
Requires user to re-enter password to confirm identity before allowing operation
Second-level auth flow:
User logged in ──► Access sensitive area ──► Trigger second-level auth
──► Re-verify (password/captcha) ──► Second-level auth success
──► Gain access permission // 1. Check if second-level auth passed
if (!StpUtil.isPermit("user:password")) {
// Not passed, redirect to verification page
}
// 2. After password verification, grant second-level auth
StpUtil.permit("user:password", 300); // Valid for 5 minutes
// 3. Access protected function
@SaCheckPermission("user:password")
@GetMapping("/api/user/password")
public String getPassword() {
return userService.getPassword(StpUtil.getLoginId());
}Reference: Sa-Token认证框架全解析 - Advanced Features
Q5: Multi-Account System Implementation
Multi-Account System: Different modules in the same application use different account systems, each with independent authentication and authorization.
admin ──► StpLogic (Backend admin system)
user ──► StpLogic (Frontend user center)
open ──► StpLogic (Open platform API) // Define three account systems
public class SaTool {
// Backend admin
public static final StpLogic ADMIN = new StpLogic("/admin");
// Frontend user
public static final StpLogic USER = new StpLogic("/user");
// Open platform
public static final StpLogic OPEN = new StpLogic("/open");
}
// Usage
SaTool.ADMIN.login(adminId);
SaTool.USER.login(userId);
SaTool.OPEN.login(openId);
// Verification
SaTool.ADMIN.checkLogin();
SaTool.USER.checkPermission("order:add");Comparison:
Instance: Single-Account: One StpUtil | Multi-Account: Multiple StpLogic
Auth Isolation: Single-Account: None | Multi-Account: Fully independent
Token Isolation: Single-Account: Share one token | Multi-Account: Independent tokens each
Scenario: Single-Account: Single application | Multi-Account: Admin/Frontend/API separation
Reference: Sa-Token认证框架全解析 - Spring Boot Integration
Q6: Distributed Deployment and Storage Options
Distributed Storage Options:
Memory: Dependency: None — Scenario: Single-node deployment — Pros: Zero config — Cons: Not distributed
Redis: Dependency: redis plugin — Scenario: Distributed deployment — Pros: High performance, mature — Cons: Requires Redis
Redisson: Dependency: redisson plugin — Scenario: Distributed + distributed locks — Pros: Rich features — Cons: Heavy dependency
InfluxDB: Dependency: influxdb plugin — Scenario: High-concurrency writes — Pros: Time-series optimized — Cons: Small ecosystem
<!-- Redis Integration -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.39.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> # Configuration
sa-token:
is-read-cookie: true # Allow Cookie token passing
is-log: false # Disable logs in productionDistributed Storage Architecture:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Service1 │ │ Service2 │ │ Service3 │
│ │ │ │ │ │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────┴──────┐
│ Redis │
│ (Unified) │
└─────────────┘
All services read/write same Redis, Token globally sharedReference: Sa-Token认证框架全解析 - Common Pitfalls
Q7: Sa-Token SSO vs OAuth2 SSO Comparison
Answer:
Protocol Standard: Sa-Token SSO: Custom protocol | OAuth2 SSO: Standard protocol
Cross-Language: Sa-Token SSO: Java only | OAuth2 SSO: Any language
Integration Difficulty: Sa-Token SSO: Low | OAuth2 SSO: Medium
Third-Party Support: Sa-Token SSO: None | OAuth2 SSO: Keycloak, Auth0, etc.
Applicable Scenario: Sa-Token SSO: Java internal systems | OAuth2 SSO: Cross-language, external exposure
Sa-Token SSO Architecture:
┌──────────┐
│ SSO Auth │
│ Center │
│ (Server) │
└────┬─────┘
│
├────► App A (Client)
├────► App B (Client)
└────► App C (Client)
OAuth2 SSO Architecture:
┌──────────┐
│ Auth │
│ Server │
│(Keycloak)│
└────┬─────┘
│
├────► App A (Any language)
├────► App B (Any language)
└────► Third-party apps (External) // Sa-Token SSO Server
@PostMapping("/doLogin")
public String doLogin(@RequestParam String name, @RequestParam String pwd) {
StpUtil.login(10001);
return SsoClientUtils.login(name, pwd);
}
// Sa-Token SSO Client
@GetMapping("/callback")
public String callback(@RequestParam String code) {
String token = SsoClientUtils.buildTokenByCode(code);
StpUtil.login(token);
return "Login successful";
}Reference: Sa-Token认证框架全解析 - Comparative Analysis
Q8: Token Passing in Frontend-Backend Separation
Token Passing Solutions:
Solution 1: Response Header return (Sa-Token default)
Login success ──► Server sets satoken in Response Header
Subsequent requests ──► Browser auto-carries Cookie
Solution 2: Response Body return
Login success ──► Return { token: "xxx" }
Frontend stores ──► localStorage or sessionStorage
Subsequent requests ──► Carry in Authorization Header
Solution 3: Cookie + Header hybrid
HttpOnly Cookie storage (prevent XSS)
Custom Header verification (prevent CSRF) // Frontend-backend separation login
@PostMapping("/login")
public R login(@RequestBody LoginRequest req) {
// Verify
User user = userService.login(req.getUsername(), req.getPassword());
StpUtil.login(user.getId());
// Get Token
String token = StpUtil.getTokenValue();
// Return to frontend (frontend stores to localStorage)
return R.ok("Login successful").data("token", token);
}
// Global interceptor
@Configuration
public class SaTokenConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(h -> {
StpUtil.checkLogin();
})).addPathPatterns("/**")
.excludePathPatterns("/login", "/static/**");
}
}Considerations:
Cookie cross-domain: Configure CORS, set withCredentials
Token refresh: Dual token or sliding renewal
XSS reads Token: Use HttpOnly Cookie or in-memory storage
CSRF attack: Carry custom Header or use SameSite
Reference: Sa-Token认证框架全解析 - Scenario Discussion
Q9: @SaCheckPermission Annotation Implementation
@SaCheckPermissionis implemented based on AOP (Aspect-Oriented Programming) , performing permission checks before method execution.
Method call ──► AOP intercept ──► Read @SaCheckPermission annotation
│
├─► StpUtil.checkPermission("user:add")
│
├─► Get user permission list from Session
│
├─► Match permission ──► Pass ──► Execute method
└─► Mismatch ──► Throw NotPermissionException // Simplified source code
@Aspect
public class SaPermissionAspect {
@Around("@annotation(saCheckPermission)")
public Object around(ProceedingJoinPoint point, SaCheckPermission saCheckPermission) throws Throwable {
// Execute permission check
StpUtil.checkPermission(saCheckPermission.value());
// Check passed, execute target method
return point.proceed();
}
} // Usage examples
@SaCheckPermission("user:add")
@PostMapping("/user")
public R addUser(@RequestBody User user) {
return R.ok("Added successfully");
}
// Multiple permissions (satisfy any one)
@SaCheckPermission(value = {"user:add", "user:update"}, mode = SaMode.OR)
@PutMapping("/user")
public R updateUser(@RequestBody User user) {
return R.ok("Updated successfully");
}
// Multiple permissions (must satisfy all)
@SaCheckPermission(value = {"user:add", "user:export"}, mode = SaMode.AND)
@GetMapping("/user/export")
public R exportUsers() {
return R.ok("Exported successfully");
}Reference: Sa-Token认证框架全解析 - Source Code Analysis
Q10: Production Security Hardening Recommendations
Security Hardening Checklist:
Token Timeout: Set reasonable expiration — Configuration: activity-timeout: 1800 (30 min)
Concurrent Login Control: Limit online devices per account — Configuration: max-login-count: 3 Password Encryption: Verify encrypted password on login — Use BCrypt
Brute Force Prevention: Limit login failure attempts — Integrate Redis rate limiting
Token Encrypted Transport: HTTPS transmission — Full-site HTTPS
Log Desensitization: Don't log passwords/sensitive info — Disable is-log
Exception Handling: Unified exception responses — Custom NotLoginException handler
sa-token:
timeout: 1800 # 30 minutes
activity-timeout: 1800 # Active timeout
max-login-count: 3 # Max 3 devices online simultaneously
is-log: false # Disable log output in production
is-read-cookie: false # Disable Cookie for frontend-backend separation
token-name: Authorization # Use Authorization Header // Unified exception handling
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotLoginException.class)
public R handleNotLogin(NotLoginException e) {
return R.error(401, "Not logged in");
}
@ExceptionHandler(NotPermissionException.class)
public R handleNotPermission(NotPermissionException e) {
return R.error(403, "No permission");
}
@ExceptionHandler(NotRoleException.class)
public R handleNotRole(NotRoleException e) {
return R.error(403, "No role");
}
}Reference: Sa-Token认证框架全解析 - Best Practices
References
Sa-Token认证框架全解析 — Original article for this interview self-test
Sa-Token Official Documentation — https://sa-token.cc/
Spring Security Official Documentation — https://docs.spring.io/spring-security/reference/index.html
RFC 6749 - OAuth 2.0 — https://tools.ietf.org/html/rfc6749
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
CodeSmart Hoops
A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
