# How to Add Custom API Signature Validation for Open APIs in ContiNew Admin

> Implement custom API signature validation for open APIs in ContiNew Admin. Extend SaSignTemplate, register your bean, and secure your endpoints with Sa-Token.

- Repository: [OpenContiNew/continew-admin](https://github.com/continew-org/continew-admin)
- Tags: how-to-guide
- Published: 2026-02-27

---

**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`](https://github.com/continew-org/continew-admin/blob/main/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.

```java
// 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`](https://github.com/continew-org/continew-admin/blob/main/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`.

```java
// 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`](https://github.com/continew-org/continew-admin/blob/main/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.

```java
// 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:

```java
// 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:

```java
// 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.

```bash
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 `sign` parameter is present, otherwise enforces standard login checks in [`SaTokenConfiguration.java`](https://github.com/continew-org/continew-admin/blob/main/SaTokenConfiguration.java).
- **Template Pattern**: `OpenApiSignTemplate` (lines 44-66) provides the default validation logic; extend this class to add custom checks like `appId` validation or IP whitelisting.
- **Automatic Registration**: `SaSignManager.setSaSignTemplate()` binds your custom template automatically when you declare it as a Spring bean.
- **Permission Bypass**: [`BaseController.java`](https://github.com/continew-org/continew-admin/blob/main/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`](https://github.com/continew-org/continew-admin/blob/main/SaTokenConfiguration.java) (lines 86-96), if `paramNames` contains `sign`, the interceptor calls `SaSignUtil.checkRequest()`; otherwise, it calls `StpUtil.checkLogin()`. Similarly, [`BaseController.java`](https://github.com/continew-org/continew-admin/blob/main/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.