Spring Cloud Gateway + Oauth2 to implement unified authentication and authentication! In fact, AT the beginning of the integration, I was not able to play, and I looked up information and source code, and finally succeeded. Recently I tried the micro-service permission solution provided by SA-Token. It feels very elegant to use. I recommend it to you!

SpringBoot e-commerce project mall (50K + STAR) address: github.com/macrozheng/…

Front knowledge

We will use Nacos as the registry, Gateway as the Gateway, and sa-Token as the micro-service permission solution, which is based on the previous solution. For those who are not familiar with these technologies, please read the following article.

  • Spring Cloud Gateway: a new generation of API Gateway services
  • Spring Cloud Alibaba: Nacos is used as a registry and configuration center
  • Spring Cloud Gateway + Oauth2 to achieve unified authentication and authentication!
  • Sa-token Usage Tutorial

Application architecture

Authentication service is responsible for login processing, gateway is responsible for login authentication and permission authentication, and other API services are responsible for handling their own business logic. To enable sa-Token sessions to be shared among multiple services, all services need to integrate SA-Token and Redis.

  • Micro-sa-token-common: a user class common to other servicesUserDTOAnd generic return result classesCommonResultThey’re drawn here.
  • Micro-sa-token-gateway: indicates the gateway service, which is responsible for request forwarding, login authentication, and permission authentication.
  • Micro-sa-token-auth: an authentication service that contains only one login interface and invokes the API implementation of sa-Token.
  • Micro-sa-token-api: indicates the protected API service. Users can access the service after passing the network authentication.

Plan implementation

The next step is to implement this solution and set up gateway service, authentication service and API service in turn.

micro-sa-token-gateway

We will first set up the gateway service, which will be responsible for the login authentication and permission authentication of the entire micro-service.

  • In addition to the generic Gateway dependencies, we also need to be inpom.xmlTo add the following dependencies, including the SA-Token Reactor responsive dependency, the Redis integrated distributed Session dependency and ourmicro-sa-token-commonRely on;
<dependencies>
   <! Sa-token Authorization (Reactor Responsive Integration) -->
   <dependency>
       <groupId>cn.dev33</groupId>
       <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
       <version>1.24.0</version>
   </dependency>
   <! -- Sa-token integration Redis (using Jackson serialization) -->
   <dependency>
       <groupId>cn.dev33</groupId>
       <artifactId>sa-token-dao-redis-jackson</artifactId>
       <version>1.24.0</version>
   </dependency>
   <! Redis connection pool -->
   <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-pool2</artifactId>
   </dependency>
   <! -- Micro-sa-Token common dependencies -->
   <dependency>
       <groupId>com.macro.cloud</groupId>
       <artifactId>micro-sa-token-common</artifactId>
       <version>1.0.0</version>
   </dependency>
</dependencies>
Copy the code
  • Next modify the configuration fileapplication.ymlAdd Redis configuration and sa-token configuration if you read the previous articleSa-token Usage TutorialThen you basically know what these configurations do;
spring:
  redis:
    database: 0
    port: 6379
    host: localhost
    password:

# Sa - Token configuration
sa-token:
  # token name (also cookie name)
  token-name: Authorization
  # Token validity period, in seconds. -1 indicates that the token will never expire
  timeout: 2592000
  # Token temporary validity period (if no operation is performed within the specified period, the token is regarded as expired), in seconds
  activity-timeout: - 1
  # allow concurrent logins with the same account (false)
  is-concurrent: true
  # Whether to share one token when multiple users log in to the same account (if false, create one token for each login)
  is-share: false
  Style # token
  token-style: uuid
  # Whether to output operation logs
  is-log: false
  Whether to read token from cookie
  is-read-cookie: false
  Whether to read the token from head
  is-read-head: true
Copy the code
  • Add an sa-Token configuration classSaTokenConfigInject a filter for login authentication and permission authentication insetAuthMethod to add routing rules insetErrorMethod to add callback processing for authentication failure.
@Configuration
public class SaTokenConfig {
    /** * Register the sa-token global filter */
    @Bean
    public SaReactorFilter getSaReactorFilter(a) {
        return new SaReactorFilter()
                // Intercept the address
                .addInclude("/ * *")
                // Open the address
                .addExclude("/favicon.ico")
                // Authentication method: access each time
                .setAuth(r -> {
                    // Login authentication: authentication is required except for the login interface
                    SaRouter.match("/ * *"."/auth/user/login", StpUtil::checkLogin);
                    // Permission authentication: Different interfaces have different access permissions
                    SaRouter.match("/api/test/hello", () -> StpUtil.checkPermission("api:test:hello"));
                    SaRouter.match("/api/user/info", () -> StpUtil.checkPermission("api:user:info"));
                })
                // setAuth method exception processing
                .setError(e -> {
                    // Set the error return format to JSON
                    ServerWebExchange exchange = SaReactorSyncHolder.getContent();
                    exchange.getResponse().getHeaders().set("Content-Type"."application/json; charset=utf-8");
                    returnSaResult.error(e.getMessage()); }); }}Copy the code
  • Extension provided under sa-TokenStpInterfaceThe interface is used to obtain the permission of the user. After the user logs in, the user information will be stored in the Session, and the permission information will also be stored in the Session, so the permission code can only be obtained from the Session.
/** * Custom permission validation interface extension */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // Returns the list of permission codes owned by this loginId
        UserDTO userDTO = (UserDTO) StpUtil.getSession().get("userInfo");
        return userDTO.getPermissionList();
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // Returns the list of role codes owned by this loginId
        return null; }}Copy the code

micro-sa-token-auth

Next, we will set up the authentication service, as long as the integration of SA-Token and the implementation of the login interface, very simple.

  • First of all inpom.xmlTo add related dependencies, including sa-token SpringBoot dependency, integration Redis to implement distributed Session dependency and ourmicro-sa-token-commonRely on;
<dependencies>
    <! -- Sa-token authentication -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-spring-boot-starter</artifactId>
        <version>1.24.0</version>
    </dependency>
    <! -- Sa-token integration Redis (using Jackson serialization) -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-dao-redis-jackson</artifactId>
        <version>1.24.0</version>
    </dependency>
    <! Redis connection pool -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <! -- Micro-sa-Token common dependencies -->
    <dependency>
        <groupId>com.macro.cloud</groupId>
        <artifactId>micro-sa-token-common</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>
Copy the code
  • Next modify the configuration fileapplication.yml, just copy the previous gateway configuration;
spring:
  redis:
    database: 0
    port: 6379
    host: localhost
    password:

# Sa - Token configuration
sa-token:
  # token name (also cookie name)
  token-name: Authorization
  # Token validity period, in seconds. -1 indicates that the token will never expire
  timeout: 2592000
  # Token temporary validity period (if no operation is performed within the specified period, the token is regarded as expired), in seconds
  activity-timeout: - 1
  # allow concurrent logins with the same account (false)
  is-concurrent: true
  # Whether to share one token when multiple users log in to the same account (if false, create one token for each login)
  is-share: false
  Style # token
  token-style: uuid
  # Whether to output operation logs
  is-log: false
  Whether to read token from cookie
  is-read-cookie: false
  Whether to read the token from head
  is-read-head: true
Copy the code
  • inUserControllerThe login interface is defined in. The Token is returned after successful loginUserServiceImplIn the class;
/** * 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("Wrong username or password");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", saTokenInfo.getTokenValue());
        tokenMap.put("tokenHead", saTokenInfo.getTokenName());
        returnCommonResult.success(tokenMap); }}Copy the code
  • inUserServiceImplTo add the logics for login, first verify the password, then notify the user ID of the sa-Token login, and then store the user information directly into 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;
        }
        // After the password verification is successful, you can log in with one line of code
        StpUtil.login(userDTO.getId());
        // Store user information in Session
        StpUtil.getSession().set("userInfo",userDTO);
        // Obtain the Token information of the current login user
        saTokenInfo = StpUtil.getTokenInfo();
        returnsaTokenInfo; }}Copy the code
  • It is important to note that sa-Token’s Session is not HttpSession, but its own session-like mechanism.

micro-sa-token-api

Next, let’s build a protected API service to implement the interface for obtaining login user information and the test interface that requires special permissions to access.

  • First of all inpom.xmlTo add dependencies, and abovemicro-sa-token-authThe same;
<dependencies>
    <! -- Sa-token authentication -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-spring-boot-starter</artifactId>
        <version>1.24.0</version>
    </dependency>
    <! -- Sa-token integration Redis (using Jackson serialization) -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-dao-redis-jackson</artifactId>
        <version>1.24.0</version>
    </dependency>
    <! Redis connection pool -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <! -- Micro-sa-Token common dependencies -->
    <dependency>
        <groupId>com.macro.cloud</groupId>
        <artifactId>micro-sa-token-common</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>
Copy the code
  • Next modify the configuration fileapplication.yml, just copy the previous gateway configuration;
spring:
  redis:
    database: 0
    port: 6379
    host: localhost
    password:

# Sa - Token configuration
sa-token:
  # token name (also cookie name)
  token-name: Authorization
  # Token validity period, in seconds. -1 indicates that the token will never expire
  timeout: 2592000
  # Token temporary validity period (if no operation is performed within the specified period, the token is regarded as expired), in seconds
  activity-timeout: - 1
  # allow concurrent logins with the same account (false)
  is-concurrent: true
  # Whether to share one token when multiple users log in to the same account (if false, create one token for each login)
  is-share: false
  Style # token
  token-style: uuid
  # Whether to output operation logs
  is-log: false
  Whether to read token from cookie
  is-read-cookie: false
  Whether to read the token from head
  is-read-head: true
Copy the code
  • Add the interface to obtain user information, because the use of Redis to achieve distributed Session, directly from the Session can obtain, is not very simple!
** Created by macro on 2020/6/19. */
@RestController
@RequestMapping("/user")
public class UserController{

    @GetMapping("/info")
    public CommonResult<UserDTO> userInfo(a) {
        UserDTO userDTO = (UserDTO) StpUtil.getSession().get("userInfo");
        returnCommonResult.success(userDTO); }}Copy the code
  • Add the needapi:test:helloPermissions to access the test interface, presetadminThe user has the permission, andmacroThere are no users.
/** * Created by macro on 2020/6/19. */
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/hello")
    public CommonResult hello(a) {
        return CommonResult.success("Hello World."); }}Copy the code

Function demonstration

After the establishment of the three services, we use Postman to demonstrate the authentication and authorization functions of micro-services.

  • Start Nacos and Redis services first, and then start againmicro-sa-token-gateway,micro-sa-token-authandmicro-sa-token-apiServices, startup order does not matter;

  • Directly through the gateway access login interface to get Token, access to the address: http://localhost:9201/auth/user/login

  • Access API services through the gateway,Without a TokenThe interface for obtaining user information is called, but cannot be accessed normally. The access address is:http://localhost:9201/api/user/info

  • Access API services through the gateway,With a TokenThe interface for obtaining user information can be accessed normally.

  • Access the API service through the gatewaymacroUser access requirementsapi:test:helloPermission test interface, cannot access, access address:http://localhost:9201/api/test/hello

  • Login switch toadminUser, the user hasapi:test:helloPermissions;

  • Access the API service through the gatewayadminThe user can access the test interface normally.

conclusion

The Sa-Token solution is simpler and more elegant than previous microservice permission solutions using Spring Security. To use Security, we need to define the authentication manager, handle the unauthenticated and unauthorized cases separately, and define the authentication and resource server configuration ourselves, which is very tedious to use. Using sa-Token, you only need to configure a filter on the gateway to implement authentication and authorization, and then invoke the API to implement login and permission assignment. The specific difference can refer to the figure below.

The resources

Official document: sa-token.dev33.cn/

Project source code address

Github.com/macrozheng/…