开箱即用!看看人家的微服务权限解决方案,那叫一个优雅

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 我们将采用Nacos作为注册中心,Gateway作为网关,使用Sa-Token提供的微服务权限解决方案,此方案是基于之前的解决方案改造的

前置知识

我们将采用Nacos作为注册中心,Gateway作为网关,使用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/user/login

  • 通过网关访问API服务,不带Token调用获取用户信息的接口,无法正常访问,访问地址:http://localhost:9201/api/user/info

  • 通过网关访问API服务,带Token调用获取用户信息的接口,可以正常访问;

  • 通过网关访问API服务,使用macro用户访问需api:test:hello权限的测试接口,无法正常访问,访问地址:http://localhost:9201/api/test/hello

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

  • 通过网关访问API服务,使用admin用户访问测试接口,可以正常访问。

总结

对比之前使用Spring Security的微服务权限解决方案,Sa-Token的解决方案更简单、更优雅。使用Security我们需要定义鉴权管理器、分别处理未认证和未授权的情况、还要自己定义认证和资源服务器配置,使用非常繁琐。而使用Sa-Token,只要在网关上配置过滤器实现认证和授权,然后调用API实现登录及权限分配即可。具体区别可以参考下图。

项目源码地址

https://github.com/macrozheng/springcloud-learning/tree/master/micro-sa-token

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
58 2
|
1月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
113 3
|
4月前
|
数据库 开发者 微服务
微服务架构下的数据一致性挑战与解决方案
在当今的软件开发领域,微服务架构因其灵活性和可扩展性而受到广泛青睐。然而,这种架构风格也带来了数据一致性的复杂问题。本文将深入探讨微服务环境中数据一致性面临的挑战,并提出一系列解决策略。我们将以实际案例分析如何应用这些策略,并讨论它们在不同场景下的利弊。文章旨在为后端开发者提供一套实用工具和方法,帮助他们在设计和实现微服务时确保数据一致性。
150 0
|
5月前
|
存储 消息中间件 运维
从单体到微服务:架构演进中的技术挑战与解决方案
在软件开发的过程中,系统架构的选择对项目的成功与否起到至关重要的作用。本文将深入探讨从单体架构向微服务架构演进过程中所遇到的技术挑战,并提供相应的解决方案。
172 0
|
3月前
|
API 微服务
【Azure 微服务】面对Service Fabric中节点状态不正常(Disabling/Warning/RemoveNode)的几种尝试解决方案
【Azure 微服务】面对Service Fabric中节点状态不正常(Disabling/Warning/RemoveNode)的几种尝试解决方案
|
4月前
|
关系型数据库 分布式数据库 数据库
PolarDB,阿里云的开源分布式数据库,与微服务相结合,提供灵活扩展和高效管理解决方案。
【7月更文挑战第3天】PolarDB,阿里云的开源分布式数据库,与微服务相结合,提供灵活扩展和高效管理解决方案。通过数据分片和水平扩展支持微服务弹性,保证高可用性,且兼容MySQL协议,简化集成。示例展示了如何使用Spring Boot配置PolarDB,实现服务动态扩展。PolarDB缓解了微服务数据库挑战,加速了开发部署,为云原生应用奠定基础。
299 3
|
5月前
|
缓存 Java 微服务
Springboot微服务整合缓存的时候报循环依赖的错误 两种解决方案
Springboot微服务整合缓存的时候报循环依赖的错误 两种解决方案
76 1
|
4月前
|
监控 Java 开发者
使用Java开发微服务架构的挑战与解决方案
使用Java开发微服务架构的挑战与解决方案
|
6月前
|
监控 持续交付 数据库
构建高性能微服务架构:挑战与解决方案
【5月更文挑战第24天】 在数字化转型的浪潮中,企业纷纷采用微服务架构以提升系统的灵活性和可扩展性。然而,随着服务的不断增多,如何保证系统性能和稳定性成为了一个严峻的挑战。本文将深入探讨在构建高性能微服务架构过程中所面临的主要问题,并提出针对性的解决方案。通过实践案例分析、技术选型对比以及性能优化策略,旨在为开发者提供一条清晰的指导思路,确保微服务系统在高并发环境下的高效运行。
|
6月前
|
API 持续交付 开发者
构建高性能微服务架构:挑战与解决方案
【4月更文挑战第29天】 随着现代软件开发的复杂性日益增加,微服务架构成为众多企业和开发者的首选。它通过将大型应用程序拆分为一系列小型、自治的服务来提供灵活性和可扩展性。然而,随之而来的是一系列的挑战,包括服务间通信、数据一致性、安全性和性能优化等。本文将深入探讨在构建高性能微服务架构过程中可能遇到的挑战,并提供针对性的解决方案,以帮助开发者克服这些难题,实现更加健壮和高效的系统。
下一篇
无影云桌面