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.

  1. Anyin CloudThe project needs an authentication framework, which is selected and usedSa-Token.
  2. Anyin CloudThe project is a microservice project, so our unified authentication needs to be placed in the authentication serviceAuthWhile unified authentication is placed on the gatewayGateway.
  3. AuthandGatewayBoth are high-frequency services that need to be lightweight, so we designed them to be database independent and not too dependent on other services,AuthServices andGatewayThe data communication of the service passesRedis.
  4. GatewayThe 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:

  1. Determine which routes need to be authenticated and which do not.
  2. If authentication is required, the system determines whether to log in. If no, an exception message is displayed.
  3. 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