# How to Implement Custom Authentication Providers Using TokenDao and GatewayAuthorizationFilter in Apache Linkis

> Learn how to implement custom authentication providers in Apache Linkis using TokenDao and GatewayAuthorizationFilter. Fetch tokens, handle validation, and integrate seamlessly into your Linkis setup.

- Repository: [The Apache Software Foundation/linkis](https://github.com/apache/linkis)
- Tags: how-to-guide
- Published: 2026-02-24

---

**To implement custom authentication providers in Linkis, create a custom `TokenDao` implementation to fetch tokens from your data source, implement a `TokenService` (or extend `CachedTokenService`) to handle validation logic, and expose it as a Spring `@Primary` bean so `GatewaySpringConfiguration` injects it into the `TokenAuthentication` static entry point.**

Apache Linkis provides a modular token-based authentication flow through its Spring Cloud Gateway. By leveraging the `TokenDao` interface and `GatewayAuthorizationFilter`, you can plug in custom authentication providers without modifying core gateway code. This guide walks through implementing custom `TokenDao` and `TokenService` implementations to integrate external token stores.

## Understanding the Linkis Token Authentication Architecture

Linkis authenticates requests through a token-based flow wired into the Spring Cloud Gateway. The core components include:

| Component | Role | Main Source File |
|-----------|------|------------------|
| **TokenDao** | Low-level data-access object that reads token data from persistence | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/java/org/apache/linkis/gateway/authentication/dao/TokenDao.java`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/java/org/apache/linkis/gateway/authentication/dao/TokenDao.java) |
| **TokenEntity** | POJO representing a token record (name, sign, allowed users/hosts) | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/java/org/apache/linkis/gateway/authentication/entity/TokenEntity.java`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/java/org/apache/linkis/gateway/authentication/entity/TokenEntity.java) |
| **TokenService** | High-level service API used by the gateway with methods like `doAuth` and `isTokenValid` | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/scala/org/apache/linkis/gateway/authentication/service/TokenService.scala`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/scala/org/apache/linkis/gateway/authentication/service/TokenService.scala) |
| **CachedTokenService** | Default `TokenService` implementation that loads tokens via `TokenDao` and caches them with Guava | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/scala/org/apache/linkis/gateway/authentication/service/CachedTokenService.scala`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-authentication/src/main/scala/org/apache/linkis/gateway/authentication/service/CachedTokenService.scala) |
| **TokenAuthentication** | Static object consulted by the gateway filter; delegates to the `TokenService` bean | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-core/src/main/scala/org/apache/linkis/gateway/security/token/TokenAuthentication.scala`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-core/src/main/scala/org/apache/linkis/gateway/security/token/TokenAuthentication.scala) |
| **GatewayAuthorizationFilter** | Global Spring Cloud Gateway filter that runs every request and executes security filters | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-spring-cloud-gateway/src/main/java/org/apache/linkis/gateway/springcloud/http/GatewayAuthorizationFilter.java`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-spring-cloud-gateway/src/main/java/org/apache/linkis/gateway/springcloud/http/GatewayAuthorizationFilter.java) |
| **GatewaySpringConfiguration** | Spring configuration that injects the `TokenService` implementation into `TokenAuthentication` | [`linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-core/src/main/scala/org/apache/linkis/gateway/config/GatewaySpringConfiguration.scala`](https://github.com/apache/linkis/blob/main/linkis-spring-cloud-services/linkis-service-gateway/linkis-gateway-core/src/main/scala/org/apache/linkis/gateway/config/GatewaySpringConfiguration.scala) |

### How the Default Authentication Flow Works

1. **Incoming request** reaches `GatewayAuthorizationFilter`.
2. The filter asks `SecurityFilter` whether the request carries a token via `TokenAuthentication.isTokenRequest`.
3. If a token is present, `TokenAuthentication.tokenAuth` is invoked.
4. `TokenAuthentication` delegates to the **singleton** `TokenService` (`CachedTokenService` by default) which:
   - Looks up the token record via `TokenDao`.
   - Performs validation (expiry, allowed users, allowed hosts).
   - Throws `TokenAuthException` if any check fails.
5. On success, the token's owner is written into the `GatewayContext` via `GatewaySSOUtils.setLoginUser`.

Because the DAO and service are **Spring beans**, you can substitute them with your own implementation, creating a custom authentication provider without touching core gateway code.

## Creating a Custom TokenDao Implementation

The `TokenDao` interface defines the contract for loading token data. To implement a custom provider, create a new DAO that reads tokens from your chosen source (e.g., external REST API, LDAP, or a different database).

```scala
// src/main/scala/com/yourcorp/linkis/auth/dao/CustomTokenDao.scala
package com.yourcorp.linkis.auth.dao

import org.apache.linkis.gateway.authentication.entity.TokenEntity
import org.apache.linkis.gateway.authentication.dao.TokenDao
import org.apache.ibatis.annotations.Param
import org.springframework.stereotype.Repository
import org.apache.http.impl.client.HttpClients
import org.apache.http.client.methods.HttpGet

@Repository
class CustomTokenDao extends TokenDao {

  private val httpClient = HttpClients.createDefault()
  private val baseUrl = "https://auth.mycorp.com/api/tokens"

  override def selectTokenByName(@Param("tokenName") tokenName: String): TokenEntity = {
    val request = new HttpGet(s"$baseUrl/name/$tokenName")
    val response = httpClient.execute(request)
    try {
      val json = scala.io.Source.fromInputStream(response.getEntity.getContent).mkString
      parseJsonToEntity(json)
    } finally {
      response.close()
    }
  }

  override def selectTokenBySign(@Param("tokenSign") tokenSign: String): TokenEntity = {
    // Implementation for sign-based lookup
    val request = new HttpGet(s"$baseUrl/sign/$tokenSign")
    // ... similar implementation
    null
  }

  private def parseJsonToEntity(json: String): TokenEntity = {
    // Parse JSON and map to TokenEntity fields
    val entity = new TokenEntity()
    // ... mapping logic
    entity
  }
}

```

**Key implementation points:**

- Annotate the class with `@Repository` so Spring can inject it.
- Implement **all** methods declared in `TokenDao` (typically `selectTokenByName` and `selectTokenBySign`).
- Return `TokenEntity` objects populated with token metadata (name, signature, allowed users, allowed hosts, expiry).

## Implementing a Custom TokenService

The `TokenService` interface defines high-level authentication operations. You can either implement the interface directly or extend `CachedTokenService` if you only need to modify specific behaviors (e.g., removing caching or adding custom validation rules).

### Extending CachedTokenService for Custom Logic

If you want to bypass the Guava cache and always query your custom DAO, or add additional validation steps:

```scala
// src/main/scala/com/yourcorp/linkis/auth/service/CustomTokenService.scala
package com.yourcorp.linkis.auth.service

import org.apache.linkis.gateway.authentication.service.TokenService
import org.apache.linkis.gateway.authentication.bo.{Token, User}
import org.apache.linkis.gateway.authentication.entity.TokenEntity
import org.apache.linkis.gateway.authentication.exception.TokenAuthException
import org.apache.linkis.gateway.authentication.errorcode.LinkisGwAuthenticationErrorCodeSummary._
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import org.springframework.context.annotation.Primary

@Service
@Primary
class CustomTokenService extends TokenService {

  @Autowired
  private var tokenDao: com.yourcorp.linkis.auth.dao.CustomTokenDao = _

  private def loadToken(tokenName: String): Token = {
    val entity = tokenDao.selectTokenByName(tokenName)
    if (entity == null) {
      throw new TokenAuthException(INVALID_TOKEN.getErrorCode, INVALID_TOKEN.getErrorDesc)
    }
    convertToToken(entity)
  }

  private def convertToEntity(token: Token): TokenEntity = {
    // Conversion logic from Token BO to TokenEntity
    null
  }

  private def convertToToken(entity: TokenEntity): Token = {
    // Conversion logic from TokenEntity to Token BO
    new Token() {
      override def getTokenName: String = entity.getTokenName
      override def getBusinessOwner: String = entity.getBusinessOwner
      override def getCreateTime: java.util.Date = entity.getCreateTime
      override def getUpdateTime: java.util.Date = entity.getUpdateTime
      override def getExpireTime: java.util.Date = entity.getExpireTime
      override def getAllowedUsers: String = entity.getAllowedUsers
      override def getAllowedHosts: String = entity.getAllowedHosts
      override def isStale: Boolean = {
        if (getExpireTime == null) false
        else getExpireTime.before(new java.util.Date())
      }
      override def isUserLegal(user: String): Boolean = {
        if (getAllowedUsers == null || getAllowedUsers.isEmpty) true
        else getAllowedUsers.split(",").contains(user)
      }
      override def isHostLegal(host: String): Boolean = {
        if (getAllowedHosts == null || getAllowedHosts.isEmpty) true
        else getAllowedHosts.split(",").contains(host)
      }
    }
  }

  override def doAuth(tokenName: String, userName: String, host: String): Boolean = {
    val token = loadToken(tokenName)
    if (token.isStale) {
      throw new TokenAuthException(TOKEN_IS_STALE.getErrorCode, TOKEN_IS_STALE.getErrorDesc)
    }
    if (!token.isUserLegal(userName)) {
      throw new TokenAuthException(ILLEGAL_TOKENUSER.getErrorCode, ILLEGAL_TOKENUSER.getErrorDesc)
    }
    if (!token.isHostLegal(host)) {
      throw new TokenAuthException(ILLEGAL_TOKENHOST.getErrorCode, ILLEGAL_TOKENHOST.getErrorDesc)
    }
    true
  }

  override def isTokenValid(tokenName: String): Boolean = {
    try {
      val token = loadToken(tokenName)
      !token.isStale
    } catch {
      case _: Exception => false
    }
  }

  override def isTokenAcceptableWithUser(tokenName: String, userName: String): Boolean = {
    try {
      val token = loadToken(tokenName)
      !token.isStale && token.isUserLegal(userName)
    } catch {
      case _: Exception => false
    }
  }

  override def getTokenByName(tokenName: String): Token = loadToken(tokenName)

  override def getTokenByUserToken(tokenName: String, tokenSign: String): Token = {
    // Implementation for sign-based lookup if needed
    loadToken(tokenName)
  }
}

```

### Wiring the Custom Service with Spring

`GatewaySpringConfiguration` obtains the `TokenService` bean via field injection and registers it with the static `TokenAuthentication` object. To ensure your implementation is used, mark it with `@Primary`:

```scala
@Service
@Primary
class CustomTokenService extends TokenService {
  // ... implementation
}

```

Alternatively, exclude the default `CachedTokenService` from component scanning or override it explicitly in a `@Configuration` class:

```scala
@Configuration
class AuthConfiguration {
  
  @Bean
  @Primary
  def tokenService(customDao: CustomTokenDao): TokenService = {
    new CustomTokenService(customDao)
  }
}

```

The `@PostConstruct` method in `GatewaySpringConfiguration` automatically calls `TokenAuthentication.setTokenService(tokenService)`, ensuring your custom logic handles all token authentication requests.

## Extending Validation with LinkisPreFilter

For additional request-level validation that runs before token authentication—such as IP allowlisting or rate limiting—implement the `LinkisPreFilter` interface. `GatewayAuthorizationFilter` automatically iterates over all registered pre-filters (around line 159 in the source), aborting the request if any filter returns `false`.

```scala
// src/main/scala/com/yourcorp/linkis/filter/IpAllowListFilter.scala
package com.yourcorp.linkis.filter

import org.apache.linkis.gateway.http.GatewayContext
import org.apache.linkis.gateway.security.LinkisPreFilter
import org.springframework.stereotype.Component

@Component
class IpAllowListFilter extends LinkisPreFilter {
  
  private val allowedIps = Set("10.0.0.1", "10.0.0.2", "192.168.1.100")
  
  override def name: String = "IpAllowListFilter"
  
  override def doFilter(gatewayContext: GatewayContext): Boolean = {
    val clientIp = gatewayContext.getRequest.getRequestRealIpAddr()
    if (allowedIps.contains(clientIp)) {
      true
    } else {
      gatewayContext.getResponse.setStatusCode(403)
      gatewayContext.getResponse.write("Access denied: IP not in allowlist")
      false
    }
  }
}

```

Register the filter as a Spring `@Component` or manually add it via `LinkisPreFilter.addFilter()` in a configuration class. The filter executes before token validation, allowing you to reject requests based on custom business rules.

## Testing Your Custom Authentication Provider

Once deployed, test the custom provider by sending requests with the `Token-Key` and `Token-User` headers:

```bash
curl -H "Token-Key: abcdef123456" \
     -H "Token-User: alice" \
     http://gateway.example.com/api/v1/engineconn/submit

```

The `GatewayAuthorizationFilter` extracts these headers and delegates to `TokenAuthentication.tokenAuth`, which invokes your `CustomTokenService.doAuth` method. If your DAO returns a valid `TokenEntity` for `abcdef123456`, and the token permits user `alice` from the requesting host, the request proceeds to the target service. Otherwise, the gateway returns a `401` or `403` error based on the specific validation failure.

## Summary

- **TokenDao** provides the data access contract for token storage; implement this interface to read from external systems like REST APIs or LDAP.
- **TokenService** defines the authentication contract; extend `CachedTokenService` or implement the interface directly to customize validation logic, caching behavior, or error handling.
- **Spring Integration** requires marking your `TokenService` with `@Primary` (or excluding the default bean) so `GatewaySpringConfiguration` injects it into the static `TokenAuthentication` entry point.
- **LinkisPreFilter** offers an extension point for pre-authentication checks such as IP allowlisting or rate limiting, executed by `GatewayAuthorizationFilter` before token validation.
- All customization happens without modifying `GatewayAuthorizationFilter` or core gateway classes, preserving Linkis's modular architecture while supporting enterprise authentication requirements.

## Frequently Asked Questions

### How does GatewayAuthorizationFilter interact with custom TokenService implementations?

`GatewayAuthorizationFilter` acts as the global entry point for all gateway requests. When it detects a token-based request (via `TokenAuthentication.isTokenRequest`), it delegates to `TokenAuthentication.tokenAuth`, which internally calls the singleton `TokenService` bean. Because `GatewaySpringConfiguration` sets this bean via `TokenAuthentication.setTokenService(tokenService)` during application startup, any custom implementation you provide (marked with `@Primary`) automatically handles all token validation requests without requiring changes to the filter itself.

### Can I use multiple authentication sources simultaneously with TokenDao?

The `TokenDao` interface is designed as a single contract per `TokenService` instance. However, you can implement a composite DAO that aggregates multiple sources internally. For example, your `selectTokenByName` method could first query a local cache, then fall back to a REST API, and finally check an LDAP directory. Since `CachedTokenService` (or your custom `TokenService`) calls the DAO methods, the service remains unaware of the underlying data federation, allowing you to support multiple authentication sources transparently.

### What is the difference between implementing TokenService directly versus extending CachedTokenService?

Extending `CachedTokenService` is appropriate when you want to retain the default Guava-based caching behavior but modify specific aspects like validation rules or error handling. `CachedTokenService` already implements the full `TokenService` contract, including cache management and DAO delegation. Implementing `TokenService` directly gives you complete control over the authentication lifecycle—including whether to use caching at all, how to handle concurrent requests, and custom exception mapping. Use direct implementation when the default caching strategy conflicts with your requirements (such as needing real-time token revocation).

### How do I troubleshoot when my custom authentication provider is not being invoked?

First, verify that your `TokenService` bean is annotated with `@Primary` to ensure it overrides the default `CachedTokenService` in the Spring context. Check the application logs during startup for the `GatewaySpringConfiguration` initialization; you should see the `TokenAuthentication.setTokenService` call completing without errors. If the gateway still uses default authentication, confirm that your custom DAO is correctly returning `TokenEntity` objects and not throwing exceptions that trigger fallback behavior. Finally, enable debug logging for `org.apache.linkis.gateway` to trace the request flow through `GatewayAuthorizationFilter` and verify that `TokenAuthentication.isTokenRequest` is detecting your token headers correctly.