头图
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.

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 class CommonResult 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 our micro-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, in setAuth adding method of routing rules, in setError 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 our micro-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 the UserServiceImpl 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 above micro-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 the macro 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 and micro-sa-token-api services. The startup sequence does not matter;

  • 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 access api: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 of api: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/

Project source code address

https://github.com/macrozheng/springcloud-learning


macrozheng
1.1k 声望1.3k 粉丝