乐趣区

关于java:开箱即用看看人家的微服务权限解决方案那叫一个优雅

记得之前写过一篇文章微服务权限终极解决方案,Spring Cloud Gateway + Oauth2 实现对立认证和鉴权!,提供了 Spring Cloud 中的权限解决方案,其实一开始整合的时候我始终玩不转,又是查资料又是看源码,最终才胜利了。最近尝试了下 Sa-Token 提供的微服务权限解决方案,用起来感觉很优雅,举荐给大家!

SpringBoot 实战电商我的项目 mall(50k+star)地址:https://github.com/macrozheng/mall

前置常识

咱们将采纳 Nacos 作为注册核心,Gateway 作为网关,应用 Sa-Token 提供的微服务权限解决方案,此计划是基于之前的解决方案革新的,对这些技术不理解的敌人能够看下上面的文章。

  • Spring Cloud Gateway:新一代 API 网关服务
  • Spring Cloud Alibaba:Nacos 作为注册核心和配置核心应用
  • 微服务权限终极解决方案,Spring Cloud Gateway + Oauth2 实现对立认证和鉴权!
  • Sa-Token 应用教程

利用架构

还是和之前计划差不多的思路,认证服务负责登录解决,网关负责登录认证和权限认证,其余 API 服务负责解决本人的业务逻辑。为了能在多个服务中共享 Sa-Token 的 Session,所有服务都须要集成 Sa-Token 和 Redis。

  • micro-sa-token-common:通用工具包,其余服务专用的用户类 UserDTO 和通用返回后果类 CommonResult 被抽取到了这里。
  • micro-sa-token-gateway:网关服务,负责申请转发、登录认证和权限认证。
  • micro-sa-token-auth:认证服务,仅蕴含一个登录接口,调用 Sa-Token 的 API 实现。
  • micro-sa-token-api:受爱护的 API 服务,用户通过网关鉴权通过后能够拜访该服务。

计划实现

接下来实现下这套解决方案,顺次搭建网关服务、认证服务和 API 服务。

micro-sa-token-gateway

咱们首先来搭建下网关服务,它将负责整个微服务的登录认证和权限认证。

  • 除了通用的 Gateway 依赖,咱们还须要在 pom.xml 中增加如下依赖,包含 Sa-Token 的 Reactor 响应式依赖,整合 Redis 实现分布式 Session 的依赖以及咱们的 micro-sa-token-common 依赖;
<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>
  • 接下来批改配置文件application.yml,增加 Redis 配置和 Sa-Token 的配置,如果你看过之前那篇 Sa-Token 应用教程的话,根本就晓得这些配置的作用了;
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
  • 增加 Sa-Token 的配置类 SaTokenConfig,注入一个过滤器用于登录认证和权限认证,在setAuth 办法中增加路由规定,在 setError 办法中增加鉴权失败的回调解决;
@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());
                });
    }
}
  • 扩大下 Sa-Token 提供的 StpInterface 接口,用于获取用户的权限,咱们在用户登录当前会把用户信息存到 Session 中去,权限信息也会在外面,所以权限码只有从 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

接下来咱们来搭建下认证服务,只有集成 Sa-Token 并实现登录接口即可,非常简单。

  • 首先在 pom.xml 中增加相干依赖,包含 Sa-Token 的 SpringBoot 依赖、整合 Redis 实现分布式 Session 的依赖以及咱们的 micro-sa-token-common 依赖;
<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>
  • 接下来批改配置文件application.yml,照抄之前网关的配置即可;
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
  • UserController 中定义好登录接口,登录胜利后返回 Token,具体实现在 UserServiceImpl 类中;
/**
 * 自定义 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);
    }
}
  • UserServiceImpl 中增加登录的具体逻辑,首先验证明码,明码校验胜利后,告诉下 Sa-Token 登录的用户 ID,而后把用户信息间接存储到 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;
    }
}
  • 这里有一点须要揭示下,Sa-Token 的 Session 并不是咱们平时了解的 HttpSession,而是它本人实现的相似 Session 的机制。

micro-sa-token-api

接下来咱们来搭建一个受爱护的 API 服务,实现获取登录用户信息的接口和须要非凡权限能力拜访的测试接口。

  • 首先在 pom.xml 中增加相干依赖,和下面的 micro-sa-token-auth 一样;
<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>
  • 接下来批改配置文件application.yml,照抄之前网关的配置即可;
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
  • 增加获取用户信息的接口,因为应用了 Redis 实现分布式 Session,间接从 Session 中获取即可,是不是非常简单!
/**
 * 获取登录用户信息接口
 * 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);
    }

}
  • 增加须要 api:test:hello 权限拜访的测试接口,预置的 admin 用户领有该权限,而 macro 用户是没有的。
/**
 * 测试接口
 * Created by macro on 2020/6/19.
 */
@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/hello")
    public CommonResult hello() {return CommonResult.success("Hello World.");
    }

}

性能演示

三个服务搭建实现后,咱们用 Postman 来演示下微服务的认证受权性能。

  • 首先启动 Nacos 和 Redis 服务,而后再启动 micro-sa-token-gatewaymicro-sa-token-authmicro-sa-token-api服务,启动程序无所谓;

  • 间接通过网关拜访登录接口获取 Token,拜访地址:http://localhost:9201/auth/us…

  • 通过网关拜访 API 服务,不带 Token调用获取用户信息的接口,无奈失常拜访,拜访地址:http://localhost:9201/api/use…

  • 通过网关拜访 API 服务,带 Token调用获取用户信息的接口,能够失常拜访;

  • 通过网关拜访 API 服务,应用 macro 用户拜访需 api:test:hello 权限的测试接口,无奈失常拜访,拜访地址:http://localhost:9201/api/tes…

  • 登录切换为 admin 用户,该用户具备 api:test:hello 权限;

  • 通过网关拜访 API 服务,应用 admin 用户拜访测试接口,能够失常拜访。

总结

比照之前应用 Spring Security 的微服务权限解决方案,Sa-Token 的解决方案更简略、更优雅。应用 Security 咱们须要定义鉴权管理器、别离解决未认证和未受权的状况、还要本人定义认证和资源服务器配置,应用十分繁琐。而应用 Sa-Token,只有在网关上配置过滤器实现认证和受权,而后调用 API 实现登录及权限调配即可。具体区别能够参考下图。

参考资料

官网文档:http://sa-token.dev33.cn/

我的项目源码地址

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

退出移动版