I remember that I wrote an article The ultimate solution for microservice permissions, Spring Cloud Gateway + Oauth2 to achieve unified authentication and authentication! , provides a permission solution in Spring Cloud. In fact, when I started the integration, I couldn't play it. I checked the information and the source code, and finally succeeded. I recently tried the microservice permission solution provided by Sa-Token. It feels very elegant to use, and I recommend it to everyone!
SpringBoot actual combat e-commerce project mall (50k+star) address: https://github.com/macrozheng/mall
Pre-knowledge
We will use Nacos as the registration center, Gateway as the gateway, and use the microservice permission solution provided by Sa-Token. This solution is based on the previous solution. Friends who don't know these technologies can read the following article.
- Spring Cloud Gateway: a new generation of API gateway services
- as the registration center and configuration center
- The ultimate solution for microservice permissions, Spring Cloud Gateway + Oauth2 realizes unified authentication and authentication!
- Sa-Token usage tutorial
Application architecture
It is the same idea as the previous solution. The authentication service is responsible for login processing, the gateway is responsible for login authentication and permission authentication, and other API services are responsible for processing their own business logic. In order to share the Sa-Token Session among multiple services, all services need to integrate Sa-Token and Redis.
- micro-sa-token-common: Common toolkit, user class
UserDTO
and common return result classCommonResult
common to other services are extracted here. - micro-sa-token-gateway: Gateway service, responsible for request forwarding, login authentication, and permission authentication.
- micro-sa-token-auth: Authentication service, which contains only one login interface, and calls the Sa-Token API implementation.
- micro-sa-token-api: A protected API service. Users can access the service after passing the gateway authentication.
Scheme realization
Next, implement this set of solutions and build gateway services, authentication services, and API services in turn.
micro-sa-token-gateway
Let's first build the gateway service, which will be responsible for the login authentication and permission authentication of the entire microservice.
- In addition to the general Gateway dependencies, we also need
pom.xml
, including Sa-Token's Reactor responsive dependency, integrating Redis to implement distributed Session dependencies, and ourmicro-sa-token-common
dependencies;
<dependencies>
<!-- Sa-Token 权限认证(Reactor响应式集成) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.24.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.24.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- micro-sa-token通用依赖 -->
<dependency>
<groupId>com.macro.cloud</groupId>
<artifactId>micro-sa-token-common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
- Next, modify the configuration file
application.yml
, add Redis configuration and Sa-Token configuration. If you read the previous Sa-Token usage tutorial , you basically know the role of these configurations;
spring:
redis:
database: 0
port: 6379
host: localhost
password:
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期,单位秒,-1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期),单位秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# 是否从cookie中读取token
is-read-cookie: false
# 是否从head中读取token
is-read-head: true
- Adding a configuration class Sa-Token
SaTokenConfig
, injecting a filter for login authentication and certification authority, insetAuth
adding method of routing rules, insetError
add callback authentication failure processing method;
@Configuration
public class SaTokenConfig {
/**
* 注册Sa-Token全局过滤器
*/
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
// 开放地址
.addExclude("/favicon.ico")
// 鉴权方法:每次访问进入
.setAuth(r -> {
// 登录认证:除登录接口都需要认证
SaRouter.match("/**", "/auth/user/login", StpUtil::checkLogin);
// 权限认证:不同接口访问权限不同
SaRouter.match("/api/test/hello", () -> StpUtil.checkPermission("api:test:hello"));
SaRouter.match("/api/user/info", () -> StpUtil.checkPermission("api:user:info"));
})
// setAuth方法异常处理
.setError(e -> {
// 设置错误返回格式为JSON
ServerWebExchange exchange = SaReactorSyncHolder.getContent();
exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
return SaResult.error(e.getMessage());
});
}
}
StpInterface
interface provided by Sa-Token is extended to obtain the user's permissions. After the user logs in, we will save the user information in the session, and the permission information will also be in it, so the permission code can be obtained from the session.
/**
* 自定义权限验证接口扩展
*/
@Component
public class StpInterfaceImpl implements StpInterface {
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 返回此 loginId 拥有的权限码列表
UserDTO userDTO = (UserDTO) StpUtil.getSession().get("userInfo");
return userDTO.getPermissionList();
}
@Override
public List<String> getRoleList(Object loginId, String loginType) {
// 返回此 loginId 拥有的角色码列表
return null;
}
}
micro-sa-token-auth
Next, let's build the authentication service, as long as it integrates Sa-Token and implements the login interface, it is very simple.
- First
pom.xml
, including Sa-Token's SpringBoot dependency, integration of Redis to achieve distributed Session dependency, and ourmicro-sa-token-common
dependency;
<dependencies>
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.24.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.24.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- micro-sa-token通用依赖 -->
<dependency>
<groupId>com.macro.cloud</groupId>
<artifactId>micro-sa-token-common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
- Next, modify the configuration file
application.yml
and copy the previous configuration of the gateway;
spring:
redis:
database: 0
port: 6379
host: localhost
password:
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期,单位秒,-1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期),单位秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# 是否从cookie中读取token
is-read-cookie: false
# 是否从head中读取token
is-read-head: true
- Define the login interface in
UserController
, and return Token after successful login. The specific implementation is in theUserServiceImpl
class;
/**
* 自定义Oauth2获取令牌接口
* Created by macro on 2020/7/17.
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserServiceImpl userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public CommonResult login(@RequestParam String username, @RequestParam String password) {
SaTokenInfo saTokenInfo = userService.login(username, password);
if (saTokenInfo == null) {
return CommonResult.validateFailed("用户名或密码错误");
}
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", saTokenInfo.getTokenValue());
tokenMap.put("tokenHead", saTokenInfo.getTokenName());
return CommonResult.success(tokenMap);
}
}
- Add the specific logic of login in
UserServiceImpl
, first verify the password, after the password verification is successful, notify the user ID of the Sa-Token login, and then store the user information directly in the Session;
/**
* 用户管理业务类
* Created by macro on 2020/6/19.
*/
@Service
public class UserServiceImpl{
private List<UserDTO> userList;
public SaTokenInfo login(String username, String password) {
SaTokenInfo saTokenInfo = null;
UserDTO userDTO = loadUserByUsername(username);
if (userDTO == null) {
return null;
}
if (!SaSecureUtil.md5(password).equals(userDTO.getPassword())) {
return null;
}
// 密码校验成功后登录,一行代码实现登录
StpUtil.login(userDTO.getId());
// 将用户信息存储到Session中
StpUtil.getSession().set("userInfo",userDTO);
// 获取当前登录用户Token信息
saTokenInfo = StpUtil.getTokenInfo();
return saTokenInfo;
}
}
- One thing to remind here is that Sa-Token's Session is not the HttpSession we usually understand, but a Session-like mechanism implemented by it itself.
micro-sa-token-api
Next, let's build a protected API service to implement an interface for obtaining login user information and a test interface that requires special permissions to access.
- First,
pom.xml
add relevant dependence, and the abovemicro-sa-token-auth
same;
<dependencies>
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.24.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.24.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- micro-sa-token通用依赖 -->
<dependency>
<groupId>com.macro.cloud</groupId>
<artifactId>micro-sa-token-common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
- Next, modify the configuration file
application.yml
and copy the previous configuration of the gateway;
spring:
redis:
database: 0
port: 6379
host: localhost
password:
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期,单位秒,-1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期),单位秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# 是否从cookie中读取token
is-read-cookie: false
# 是否从head中读取token
is-read-head: true
- Add an interface to obtain user information. Since Redis is used to implement a distributed session, it can be obtained directly from the session. Isn't it very simple!
/**
* 获取登录用户信息接口
* Created by macro on 2020/6/19.
*/
@RestController
@RequestMapping("/user")
public class UserController{
@GetMapping("/info")
public CommonResult<UserDTO> userInfo() {
UserDTO userDTO = (UserDTO) StpUtil.getSession().get("userInfo");
return CommonResult.success(userDTO);
}
}
- Add a test interface that
api:test:hello
admin
has this permission, but themacro
does not.
/**
* 测试接口
* Created by macro on 2020/6/19.
*/
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/hello")
public CommonResult hello() {
return CommonResult.success("Hello World.");
}
}
Demo
After the three services are built, we use Postman to demonstrate the authentication and authorization functions of the microservices.
- Start the Nacos and Redis services first, and then start the
micro-sa-token-gateway
,micro-sa-token-auth
andmicro-sa-token-api
services. The startup sequence does not matter;
- Access the login interface directly through the gateway to obtain the Token, the access address: http://localhost:9201/auth/user/login
- Access API service through the gateway,
does not have Token to call the interface to obtain user information, and it cannot be accessed normally. The access address is: http://localhost:9201/api/user/info
- Access the API service through the gateway,
calls the interface for obtaining user information with Token, and can be accessed normally;
- Access the API service through the gateway, use
macro
user to accessapi:test:hello
permission, and it cannot be accessed normally. The access address is: http://localhost:9201/api/test/hello
- The login is switched to
admin
, who has the permission ofapi:test:hello
- Access the API service through the gateway, and use the
admin
user to access the test interface, which can be accessed normally.
Summarize
Compared with the previous microservice permission solution using Spring Security, the Sa-Token solution is simpler and more elegant. To use Security, we need to define an authentication manager, handle unauthenticated and unauthorized situations separately, and define the authentication and resource server configuration ourselves, which is very cumbersome to use. With Sa-Token, you only need to configure a filter on the gateway to implement authentication and authorization, and then call the API to implement login and permission assignment. Refer to the figure below for specific differences.
Reference
Official document: http://sa-token.dev33.cn/
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。