How to Add Custom API Signature Validation for Open APIs in ContiNew Admin
To add custom API signature validation for open APIs in ContiNew Admin, extend SaSignTemplate (or the existing OpenApiSignTemplate) to implement your validation logic, register it as a Spring bean, and let SaTokenConfiguration bind it to SaSignManager, which the Sa-Token interceptor automatically invokes when requests contain a sign parameter.
The ContiNew Admin repository (available at continew-org/continew-admin) provides a robust framework for securing open APIs through request signature validation powered by Sa-Token. Understanding how to add custom API signature validation for open APIs allows developers to implement additional security layers—such as IP whitelisting, request body hashing, or extended parameter validation—without modifying the core authentication flows.
How Signature Validation Works in ContiNew Admin
The signature validation system relies on three coordinated components: the Sa-Token interceptor that routes requests, the template that validates signatures, and the controller layer that bypasses standard permissions for validated open API calls.
Sa-Token Interceptor and Request Routing
In continew-server/src/main/java/top/continew/admin/config/satoken/SaTokenConfiguration.java, the saInterceptor bean inspects every incoming request. At lines 86-96, the logic checks for the presence of a sign parameter to determine whether to validate via signature or standard login authentication.
// SaTokenConfiguration.java (lines 86-96)
if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) {
SaSignUtil.checkRequest(saRequest);
} else {
StpUtil.checkLogin();
}
If the request contains a sign parameter, the interceptor delegates to SaSignUtil.checkRequest(), which uses the template registered in SaSignManager. At line 82 of the same file, the configuration registers the active template via SaSignManager.setSaSignTemplate(signTemplate).
Core Validation Logic in OpenApiSignTemplate
The default implementation resides in continew-plugin/continew-plugin-open/src/main/java/top/continew/admin/open/sign/OpenApiSignTemplate.java. This class extends SaSignTemplate and overrides checkParamMap (lines 44-66) to enforce mandatory parameters including timestamp, nonce, sign, and accessKey.
// OpenApiSignTemplate.java (lines 44-66 conceptual)
public void checkParamMap(Map<String, String> paramMap) {
// timestamp, nonce, sign, accessKey are mandatory
// ... load AppDO by accessKey, verify status, expiration
// validate timestamp & nonce via super methods
// inject secret key and verify signature
}
The method loads the application configuration by accessKey, verifies the application status and expiration, validates the timestamp and nonce through parent class methods, then injects the secret key to verify the cryptographic signature.
Controller-Level Permission Bypass
Controllers skip standard permission checks when handling signed requests. In continew-common/src/main/java/top/continew/admin/common/base/controller/BaseController.java (lines 57-62), the presence of a sign parameter triggers an immediate return, treating the request as an authenticated open API call.
// BaseController.java (lines 57-62)
if (paramNames.stream().anyMatch(SaSignTemplate.sign::equals)) {
return; // skip normal permission logic
}
Implementing Custom API Signature Validation
To introduce custom validation logic—such as additional parameter checks, IP restrictions, or custom cryptographic algorithms—you create a new template implementation and register it with the Sa-Token manager.
Extend SaSignTemplate for Custom Logic
Create a new class extending OpenApiSignTemplate (or directly extending SaSignTemplate) to add validation steps. For example, to require and validate an additional appId parameter:
// src/main/java/top/continew/admin/open/sign/CustomApiSignTemplate.java
package top.continew.admin.open.sign;
import cn.dev33.satoken.sign.template.SaSignTemplate;
import org.springframework.stereotype.Component;
import top.continew.admin.open.service.AppService;
import top.continew.starter.core.util.validation.ValidationUtils;
import java.util.Map;
/**
* Custom signature validator that requires an extra {@code appId} parameter.
*/
@Component
public class CustomApiSignTemplate extends SaSignTemplate {
private final AppService appService;
public CustomApiSignTemplate(AppService appService) {
this.appService = appService;
}
@Override
public void checkParamMap(Map<String, String> paramMap) {
// reuse the default OpenAPI checks
super.checkParamMap(paramMap); // validates timestamp, nonce, sign, accessKey
// ---- custom part ----
String appId = paramMap.get("appId");
ValidationUtils.throwIfBlank(appId, "appId不能为空");
// you could load extra data based on appId, e.g. verify it belongs to the same tenant
ValidationUtils.throwIfNull(appService.getById(Long.valueOf(appId)),
"appId无效或已被删除");
// further custom validation can be added here
}
@Override
public String createSign(Map<String, ?> paramMap) {
// you may want to include the extra appId when generating the sign
return super.createSign(paramMap); // default MD5 of sorted params
}
}
Because SaTokenConfiguration already sets the bean found in the context (signTemplate) to SaSignManager, the new class automatically replaces the previous template.
Register the Custom Template
The SaTokenConfiguration automatically registers any SaSignTemplate bean via SaSignManager.setSaSignTemplate(signTemplate) at line 82. Simply annotating your class with @Component ensures Spring injects it into the configuration and binds it to the Sa-Token manager.
Route-Specific Validation (Optional)
If you need separate validation logic for specific URL prefixes (for example, /open/v2/** vs /open/v1/**), create a custom configuration class that registers a dedicated interceptor:
// src/main/java/top/continew/admin/config/satoken/CustomSaTokenConfiguration.java
package top.continew.admin.config.satoken;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.sign.SaSignManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import top.continew.admin.open.sign.CustomApiSignTemplate;
@Configuration
public class CustomSaTokenConfiguration {
@Bean
public SaInterceptor customSaInterceptor(CustomApiSignTemplate customTemplate) {
// Register the custom template only for paths under /open/**
SaSignManager.setSaSignTemplate(customTemplate);
return new SaInterceptor(handle -> SaRouter.match("/open/**")
.check(r -> SaSignUtil.checkRequest(r)));
}
}
Testing Your Custom Validation
When calling the open API from a client, you must include all required parameters and compute the signature using the same algorithm (MD5 of sorted parameters plus the secret key). The secret key is looked up by accessKey inside your template implementation.
curl -G "https://api.example.com/open/user/list" \
-d "timestamp=$(date +%s)" \
-d "nonce=$(uuidgen)" \
-d "accessKey=demoKey123" \
-d "appId=42" \
-d "sign=$(java -cp your-client.jar top.continew.admin.open.util.SignUtil \
--timestamp $timestamp --nonce $nonce --accessKey demoKey123 --appId 42)"
Summary
- Sa-Token Interceptor: Routes requests to signature validation when the
signparameter is present, otherwise enforces standard login checks inSaTokenConfiguration.java. - Template Pattern:
OpenApiSignTemplate(lines 44-66) provides the default validation logic; extend this class to add custom checks likeappIdvalidation or IP whitelisting. - Automatic Registration:
SaSignManager.setSaSignTemplate()binds your custom template automatically when you declare it as a Spring bean. - Permission Bypass:
BaseController.java(lines 57-62) skips permission checks for signed requests, treating them as authenticated open API calls. - Flexible Routing: Create custom interceptor configurations to apply different validation templates to specific URL prefixes.
Frequently Asked Questions
What parameters are required for open API signature validation?
The default OpenApiSignTemplate requires four parameters: timestamp, nonce, sign, and accessKey. The timestamp prevents replay attacks, nonce ensures request uniqueness, accessKey identifies the calling application, and sign contains the cryptographic hash. When implementing custom validation, you can add mandatory parameters such as appId by extending checkParamMap and validating them before calling super.checkParamMap().
How does the system differentiate between open API and regular API requests?
The system checks for the presence of a sign parameter. In SaTokenConfiguration.java (lines 86-96), if paramNames contains sign, the interceptor calls SaSignUtil.checkRequest(); otherwise, it calls StpUtil.checkLogin(). Similarly, BaseController.java (lines 57-62) skips permission annotation checks when a sign is detected, routing the request through the signature validation pathway instead of the standard authentication flow.
Can I use multiple signature templates for different API endpoints?
Yes. While SaSignManager holds a single global template by default, you can create custom interceptor configurations (as shown in the route-specific example) that manually call SaSignManager.setSaSignTemplate() with different template instances before invoking SaSignUtil.checkRequest(). Alternatively, inspect the request URL within your single template's checkParamMap method to apply conditional validation logic based on the endpoint.
Where is the secret key stored and validated?
The secret key is stored in the application database and loaded via AppService in OpenApiSignTemplate. During validation (inside checkParamMap), the template retrieves the AppDO record using the provided accessKey, verifies the application is active and not expired, then uses the stored secretKey to validate the signature. For custom implementations, you can modify this lookup logic to retrieve secrets from external vaults or cache systems.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →