Simplify Spring Cloud Gateway Authentication with Sa-Token: A Step‑by‑Step Demo

This tutorial shows how to replace heavyweight Spring Security with the lightweight Sa-Token framework by demonstrating token generation, configuration, gateway filtering, role‑based access control, and asynchronous permission fetching for a Spring Cloud microservices architecture.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Simplify Spring Cloud Gateway Authentication with Sa-Token: A Step‑by‑Step Demo

Requirement Analysis

Traditional authentication and authorization require writing a lot of boilerplate code; Spring Security is heavy and its many configuration options are hard to understand, while a custom implementation forces you to handle token generation, validation, refresh, and permission assignment yourself.

Architecture

Architecture diagram
Architecture diagram

Authentication with Sa‑Token

Sa‑Token Module

Generate a token and create a session using the simple API: StpUtil.login(Object id); The generated token is stored as a Token credential together with a Session object.

Configuration

server:
  port: 8081
spring:
  application:
    name: weishuang-account
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/weishuang_account?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
    username: root
    password: root
redis:
  database: 0
  host: 127.0.0.1
  port: 6379
  timeout: 10s
  lettuce:
    pool:
      max-active: 200
      max-wait: -1ms
      max-idle: 10
      min-idle: 0
sa-token:
  token-name: weishuang-token
  timeout: 2592000
  activity-timeout: -1
  is-concurrent: true
  is-share: true
  token-style: uuid
  is-log: false
  token-prefix: Bearer

In the configuration we set token-name to define the cookie name and token-prefix to prepend Bearer to the token when it is sent in the HTTP header.

When calling an API the header should contain: weishuang-token = Bearer token123456 Sa‑Token’s session mode stores sessions in Redis, which is required for distributed services; alternatively, Sa‑Token also supports JWT for a stateless token.

Login API Example

User Entity

@Data
public class User {
    private String id;
    private String userName;
    private String password;
}

UserController

@RestController
@RequestMapping("/account/user/")
public class UserController {
    @Autowired
    private UserManager userManager;

    @PostMapping("doLogin")
    public SaResult doLogin(@RequestBody AccountUserLoginDTO req) {
        userManager.login(req);
        return SaResult.ok("登录成功");
    }
}

UserManager Implementation

@Component
public class UserManagerImpl implements UserManager {
    @Autowired
    private UserService userService;

    @Override
    public void login(AccountUserLoginDTO req) {
        String password = PasswordUtil.generatePassword(req.getPassword());
        User user = userService.getOne(req.getUserName(), password);
        if (user == null) {
            throw new RuntimeException("账号或密码错误");
        }
        StpUtil.login(user.getId());
        StpUtil.getSession().set("USER_DATA", user);
    }
}

UserService

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private UserMapper userMapper;

    public User getOne(String username, String password) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName, username)
                    .eq(User::getPassword, password);
        return userMapper.selectOne(queryWrapper);
    }
}

Gateway Module

Dependencies

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
        <version>1.34.0</version>
    </dependency>
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-dao-redis-jackson</artifactId>
        <version>1.34.0</version>
    </dependency>
</dependencies>

Gateway Configuration

server:
  port: 9000
spring:
  application:
    name: weishuang-gateway
cloud:
  loadbalancer:
    ribbon:
      enabled: false
  nacos:
    discovery:
      username: nacos
      password: nacos
      server-addr: localhost:8848
  gateway:
    routes:
      - id: account
        uri: lb://weishuang-account
        order: 1
        predicates:
          - Path=/account/**
redis:
  database: 0
  host: 127.0.0.1
  port: 6379
  timeout: 10s
  lettuce:
    pool:
      max-active: 200
      max-wait: -1ms
      max-idle: 10
      min-idle: 0
sa-token:
  token-name: weishuang-token
  timeout: 2592000
  activity-timeout: -1
  is-concurrent: true
  is-share: true
  token-style: uuid
  is-log: false
  token-prefix: Bearer

The same Sa‑Token and Redis settings must be applied in the gateway to share session data with the account service.

Authentication Filter

package com.weishuang.gateway.gateway.config;

import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SaTokenConfigure {
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
            .addInclude("/**")
            .addExclude("/favicon.ico")
            .setAuth(obj -> {
                SaRouter.match("/**", "/account/user/doLogin", r -> StpUtil.checkLogin());
            })
            .setError(e -> SaResult.error(e.getMessage()));
    }
}

All paths are intercepted; the login endpoint is excluded so that users can obtain a token.

Fine‑Grained Authorization (RBAC)

Beyond simple token validation, we need to verify whether a user has permission to access a specific path. In a classic RBAC model a user can have multiple roles, and each role can have multiple permissions.

RBAC diagram
RBAC diagram

Sa‑Token provides the StpInterface to plug in custom role and permission retrieval logic.

@Component
public class StpInterfaceImpl implements StpInterface {
    @Autowired
    private RoleFacade roleFacade;
    @Autowired
    private PermissionFacade permissionFacade;
    @Autowired
    private ThreadPollConfig threadPollConfig;

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        Object res = StpUtil.getTokenSession().get("PERMS");
        if (res == null) {
            CompletableFuture<List<String>> permFuture = CompletableFuture.supplyAsync(() -> {
                List<PermissionDTO> permissions = permissionFacade.getPermissions((String) loginId);
                return permissions.stream().map(PermissionDTO::getPath).collect(Collectors.toList());
            }, threadPollConfig.USER_ROLE_PERM_THREAD_POOL);
            try {
                return permFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
        String paths = (String) res;
        return ListUtil.string2List(paths);
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        Object res = StpUtil.getTokenSession().get("ROLES");
        if (res == null) {
            CompletableFuture<List<String>> roleFuture = CompletableFuture.supplyAsync(() -> {
                List<RoleDTO> roles = roleFacade.getRoles((String) loginId);
                return roles.stream().map(RoleDTO::getRoleName).collect(Collectors.toList());
            }, threadPollConfig.USER_ROLE_PERM_THREAD_POOL);
            try {
                return roleFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
        String roleNames = (String) res;
        return ListUtil.string2List(roleNames);
    }
}

The implementation caches role and permission data in Redis to avoid repeated remote calls.

Gateway Role/Permission Retrieval

package com.weishuang.gateway.gateway.config;

import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Configuration
public class ThreadPollConfig {
    private final BlockingQueue<Runnable> asyncSenderThreadPoolQueue = new LinkedBlockingQueue<>(50000);
    public final ExecutorService USER_ROLE_PERM_THREAD_POOL = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors(),
            1000 * 60,
            TimeUnit.MILLISECONDS,
            this.asyncSenderThreadPoolQueue,
            new ThreadFactory() {
                private final AtomicInteger threadIndex = new AtomicInteger(0);
                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "RolePermExecutor_" + threadIndex.incrementAndGet());
                }
            }
    );
}

WebFlux Filter Example

@Component
public class ForwardAuthFilter implements WebFilter {
    static Set<String> whitePaths = new HashSet<>();
    static {
        whitePaths.add("/account/user/doLogin");
        whitePaths.add("/account/user/logout");
        whitePaths.add("/account/user/register");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        String path = exchange.getRequest().getPath().toString();
        if (!whitePaths.contains(path)) {
            if (!StpUtil.hasPermission(path)) {
                throw new NotPermissionException(path);
            }
        }
        return chain.filter(exchange);
    }
}

Alternatively, a fully reactive approach can fetch permissions via a WebClient call instead of implementing StpInterface.

Conclusion

With only a few lines of Sa‑Token code— StpUtil.login(Object id) —the framework handles token creation, session management, role and permission caching, and integrates seamlessly with Spring Cloud Gateway, eliminating the need for complex Spring Security configurations.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AuthenticationgatewaySpring CloudSa-Token
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

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.