This is the seventh day of my participation in the First Challenge 2022. For details: First Challenge 2022.
The introduction
Hello, this is Anyin.
In my previous post, I don’t know if you noticed that my code is in the Anyin Cloud project (please click on the star). In this project, I have accumulated some small tools and best practices in my daily work. With the progress of time, this project has gradually become a scaffolding for my personal construction project, which can quickly build a perfect micro-service infrastructure based on Spring Cloud technology stack.
I saw an authorization authentication framework sa-Token on the Nuggets before. After a brief understanding, I found that it is really easy to use and has rich functions. Today let’s integrate it into the Anyin Cloud project.
Need to comb
Before integrating Sa-Token into our project, we need to sort out the requirements first, not for integration’s sake.
Anyin Cloud
The project needs an authentication framework, which is selected and usedSa-Token
.Anyin Cloud
The project is a microservice project, so our unified authentication needs to be placed in the authentication serviceAuth
While unified authentication is placed on the gatewayGateway
.Auth
andGateway
Both are high-frequency services that need to be lightweight, so we designed them to be database independent and not too dependent on other services,Auth
Services andGateway
The data communication of the service passesRedis
.Gateway
The service needs to pass the identity of the currently logged in user downstream.
Sa – Token integration
Integrated authentication Auth service
First, let’s deal with the Auth service.
Add the pom.xml dependency because we need Redis for data communication, so we need to rely on the corresponding Redis component.
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
</dependency>
Copy the code
Write the Login method in the Auth service. According to our previous requirements, Auth needs to be lightweight enough, so that it does not rely on the database. When logging in, Auth needs user information, which can be obtained through user service UPMS remote call.
@Component
@Slf4j
public class UsernameLoginHandler implements LoginHandler {
@Autowired
private SysUserFeignApi sysUserFeignApi;
@Override
public LoginTypeEnum extension(a) {
return LoginTypeEnum.USERNAME;
}
@Override
public LoginUserDTO login(String... content) {
// TODO param check
String username = content[0];
String password = content[1];
SysUserResp sysUser = sysUserFeignApi.infoByUsername(username).getData();
if(sysUser == null) {throw AuthExCodeEnum.USER_NOT_REGISTER.getException();
}
// TODO add salt
if(! sysUser.getPassword().equals(SecureUtil.md5(password))){throw AuthExCodeEnum.USER_PASSWORD_ERROR.getException();
}
if(UserStatusEnum.DISABLE.getCode().equals(sysUser.getStatus())){
throw AuthExCodeEnum.USER_IS_DISABLE.getException();
}
sysUser.setPassword(null);
StpUtil.login(sysUser.getId());
SaTokenInfo token = StpUtil.getTokenInfo();
LoginUserDTO user = new LoginUserDTO();
user.setSysUser(sysUser);
user.setToken(token);
returnuser; }}Copy the code
According to the proposed sa-token usage, after the user password is verified, stputil.login is used to login to the framework, which is actually the corresponding information in Redis. Two messages will be recorded on Redis:
- The user ID,
satoken:login:session:
At the beginning, its value will also contain some additional information - Login Token,
satoken:login:token:
At the beginning, its value is the user ID
After login, we also need to get the token back to the front end, so we use stputil.getTokenInfo () to get the token information, and finally assemble the user information and token information back to the front end.
Once the code is written, we need to test the login interface.
Okay, we’ve taken care of login authentication, isn’t it easy? Sweet not sweet?
Integrated Gateway authentication service
We move on to the authentication Gateway service. As always, add dependencies first.
This is particularly important because our Gateway is the Spring Cloud Gateway and the underlying WebFlux implementation is programmed based on the Reactor model; The previous Auth service was a normal SpringMVC service, programmed based on the Servlet model.
So we introduce sa-token-reactor-spring-boot-starter
<! -- sa-token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
</dependency>
Copy the code
Next, we continue to write the Gateway service filter, where we do three main things:
- Determine which routes need to be authenticated and which do not.
- If authentication is required, the system determines whether to log in. If no, an exception message is displayed.
- If you log in, the user ID is transparently transmitted to the downstream service
To determine which routes need authentication, you can configure the metadata of a dynamic route to determine whether the current route needs authentication. The code is as follows:
Route route = (Route)exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
Integer needAuth =(Integer) route.getMetadata().get(GatewayConstants.SYS_ROUTE_AUTH_KEY);
// Route without authentication
if(! NEED_AUTH.equals(needAuth)){return chain.filter(exchange);
}
Copy the code
When a route needs to be authenticated, the isLogin method provided by the sa-token is used to check whether the route is logged in. If the route is not logged in, an exception message is displayed. As follows:
// Determine whether to log in
if(! StpUtil.isLogin()){return this.response(exchange, CommonExCodeEnum.USER_NOT_LOGIN.getException());
}
Copy the code
If the requested user is already logged in, use the stputil. getLoginId method to get the current user ID and pass it to the downstream service as follows:
private ServerWebExchange setHeaderLoginId(ServerWebExchange exchange, String loginId){
ServerHttpRequest request = exchange.getRequest().mutate().header(CommonConstants.USER_ID, loginId).build();
return exchange.mutate().request(request).build();
}
Copy the code
Last but not least, we need to register the global filter. In addition to our own filter, we also need to register the sa-token filter. The StpUtil utility class provides simple methods, thanks to the need to register a global filter, SaReactorFilter, which processes a lot of context information and corresponding logic internally. So, we need to register two filters, as follows:
@Bean
public GatewayAuthFilter gatewayAuthFilter(a){
return new GatewayAuthFilter();
}
@Bean
public SaReactorFilter saReactorFilter(a){
return new SaReactorFilter();
}
Copy the code
In fact, the SaReactorFilter provides many methods and parameters to handle various business scenarios, but I do not use some of its internal methods because I may need more customized logic processing for the filter.
test
With the above two pieces of code completed, we need to test it to see if the framework is a good thing.
First, test whether a non-login exception will be reported in the scenario where the token is not passed.
In the scenario of token transfer, information can be returned normally
The last
Thanks to the sa-Token authors for providing such a beefy framework. The document address is sa-token.
These are the steps for Spring Cloud Gateway to integrate SA-Token. The following will be in-depth sa-Token source code, learn more about the use of methods and design ideas, please look forward to.
Anyin Cloud