Integrating Sa-Token Authentication in Spring Cloud Alibaba Gateway and Authorization Service
This article provides a step‑by‑step guide on configuring Sa‑Token for login, permission, SSO and distributed session management in a Spring Cloud Alibaba gateway and a separate authorization service, including Maven dependencies, bootstrap settings, global filters, exception handling, role/permission interfaces, and testing procedures.
Sa-Token is a lightweight Java permission authentication framework that provides login authentication, permission verification, SSO, OAuth2.0, distributed session, and microservice gateway authentication.
The article demonstrates how to configure a gateway service and an authorization service using Spring Cloud Alibaba, Spring Boot 2.1.13, Sa-Token 1.30.0, and Redis.
Gateway configuration
1. Add required dependencies in pom.xml (Sa-Token reactor starter, Redis DAO, Spring Cloud Gateway).
<!-- Sa-Token 权限认证(Reactor响应式集成) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.30.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.30.0</version>
</dependency>
<!-- GateWay 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>2. Add Sa-Token settings to bootstrap.yml (token name, timeout, concurrency, etc.).
# Sa-Token配置
sa-token:
token-name: satoken
timeout: 2592000
activity-timeout: -1
is-concurrent: true
is-share: false
token-style: uuid
is-log: false
is-read-cookie: false
is-read-head: true3. Implement a global filter SaTokenConfigure to register SaReactorFilter with include/exclude paths, authentication logic using SaRouter , error handling, and CORS headers.
package com.frontop.meta.config;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.frontop.meta.util.ResultJsonUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.server.ServerWebExchange;
@Configuration
public class SaTokenConfigure {
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
.addInclude("/**")
.addExclude("/favicon.ico")
.setAuth(obj -> {
SaRouter.match("/**", "/meta-auth/phoneLogin", r -> StpUtil.checkLogin());
SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
SaRouter.match("/meta-system/**", r -> StpUtil.checkPermission("system-no"));
SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
})
.setError(e -> {
ServerWebExchange exchange = SaReactorSyncHolder.getContext();
exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
return SaResult.error(e.getMessage());
})
.setBeforeAuth(obj -> {
SaHolder.getResponse()
.setHeader("Access-Control-Allow-Origin", "*")
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
.setHeader("Access-Control-Max-Age", "3600")
.setHeader("Access-Control-Allow-Headers", "*");
SaRouter.match(SaHttpMethod.OPTIONS)
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
.back();
});
}
}4. Create a global exception handler GlobalException to map Sa‑Token exceptions to custom JSON responses.
package com.frontop.meta.config;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.frontop.meta.constant.ResponseCodeConstant;
import com.frontop.meta.constant.ResponseMessageConstant;
import com.frontop.meta.util.ResultJsonUtil;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@ExceptionHandler
public class GlobalException {
@ResponseBody
@ExceptionHandler
public ResultJsonUtil
handlerException(Exception e) {
e.printStackTrace();
ResultJsonUtil
re = null;
if (e instanceof NotLoginException) {
re = new ResultJsonUtil().customized(ResponseCodeConstant.OAUTH_TOKEN_FAILURE, ResponseMessageConstant.OAUTH_TOKEN_MISSING, null);
} else if (e instanceof NotRoleException) {
NotRoleException ee = (NotRoleException) e;
re = new ResultJsonUtil().customized(ResponseCodeConstant.OAUTH_TOKEN_DENIED, "无此角色:" + ee.getRole(), null);
} else if (e instanceof NotPermissionException) {
NotPermissionException ee = (NotPermissionException) e;
re = new ResultJsonUtil().customized(ResponseCodeConstant.OAUTH_TOKEN_DENIED, "无此角色:" + ee.getCode(), null);
} else if (e instanceof DisableLoginException) {
DisableLoginException ee = (DisableLoginException) e;
re = new ResultJsonUtil().customized(ResponseCodeConstant.USER_LOCK, "账号被封禁:" + ee.getDisableTime() + "秒后解封", null);
} else {
re = new ResultJsonUtil().fail(e.getMessage());
}
return re;
}
}5. Implement StpInterfaceImpl to provide mock role and permission lists for the logged‑in user.
package com.frontop.meta.config;
import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StpInterfaceImpl implements StpInterface {
@Override
public List
getPermissionList(Object loginId, String loginType) {
List
strs = new ArrayList<>();
strs.add("system");
return strs;
}
@Override
public List
getRoleList(Object loginId, String loginType) {
List
strs = new ArrayList<>();
strs.add("admin");
return strs;
}
}Authorization service configuration
Repeat the same Sa‑Token dependencies and bootstrap.yml settings in the system service, then add a simple login controller using StpUtil.login and token retrieval.
package com.frontop.meta.controller;
import cn.dev33.satoken.stp.StpUtil;
import com.frontop.meta.util.ResultJsonUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Api(tags = "用户授权登录")
public class UserLoginController {
@ApiOperation(value = "手机+密码登录")
@PostMapping("/phoneLogin")
public ResultJsonUtil
getAwardCount(String phone, String password) {
if (phone.equals("18874288923") && password.equals("123")) {
StpUtil.login(1001, "PC");
return new ResultJsonUtil().success(StpUtil.getTokenInfo());
}
return new ResultJsonUtil().fail("手机号或密码错误");
}
}Testing shows token generation, storage in Redis, and access control enforced by the gateway. Requests without a token or without required roles/permissions are blocked, while authorized requests succeed.
Finally, the article shows how to enable annotation‑based interception with SaAnnotationInterceptor and configure CORS in the gateway.
package com.geo.gateway.config;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedMethod("*");
corsConfig.addAllowedOrigin("*");
corsConfig.addAllowedHeader("*");
corsConfig.setAllowCredentials(true);
source.registerCorsConfiguration("/**", corsConfig);
return new CorsWebFilter(source);
}
}Source repository: https://gitee.com/yangjial/meta.git (frontop branch).
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.