springCloud整合gateway、satoken认证鉴权(包括问题解释)

springCloud整合gateway、satoken认证鉴权(包括问题解释)

码农世界 2024-06-07 后端 94 次浏览 0个评论
注意:本文已整合nacos注册中心(最简单模式),在看之前先整合喔。

一、整体介绍

关于springcloud的gateway介绍我就不赘述了,网上的相关文章很多。给大家贴一个:

Gateway网关简介及使用-CSDN博客

1、关于satoken的简介

  • Sa-Token 是一款轻量级的Java权限认证框架,可以用来解决登录认证、权限认证、Session会话、单点登录、OAuth2.0、微服务网关鉴权等一系列权限相关问题。

  • 框架集成简单、最简单的可以使用一个依赖即可完成,API设计优雅,通过 Sa-Token,你将以一种极其简单的方式实现系统的权限认证部分。

    相信看到这里的同学已经对他有了初步的了解,下面我们直接开始进行整合。

    二、整合思路

    将satoken和gateway分为两个微服务,satoken模块主要用来做登录、登出等用户相关的东西(也可将用户相关的东西抽象出来一个模块,使用openFeign进行远程调用,看自己的想法)等;gateway模块用来判断用户请求转发前是否是出于登录状态,粗颗粒度的鉴权(本文未做);

    接下来开始整合;

    三、项目结构与代码

    1、整体结构

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

    父级模块引入的具体依赖为:

     
            1.8
            UTF-8
            UTF-8
            2.6.13
            2021.0.5.0
            2021.0.5
        
        
            
                org.springframework.boot
                spring-boot-starter-jdbc
            
            
                com.mysql
                mysql-connector-j
            
            
                org.projectlombok
                lombok
                true
            
            
                org.springframework.boot
                spring-boot-starter-test
                test
            
            
            
                com.baomidou
                mybatis-plus-boot-starter
                3.5.1
            
            
            
                com.github.xiaoymin
                knife4j-spring-boot-starter
                3.0.2
            
            
            
                com.alibaba
                fastjson
                2.0.11
            
            
            
                com.alibaba
                druid-spring-boot-starter
                1.2.14
            
            
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
            
        
        
            
                
                    org.springframework.boot
                    spring-boot-dependencies
                    ${spring-boot.version}
                    pom
                    import
                
                
                
                    com.alibaba.cloud
                    spring-cloud-alibaba-dependencies
                    ${spring-cloud-alibaba.version}
                    pom
                    import
                
                
                
                    org.springframework.cloud
                    spring-cloud-dependencies
                    ${spring-cloud.version}
                    pom
                    import
                
            
        

    有几个依赖可能是多余的但是不影响(尽量不要动他,防止出问题)

    2、common模块

    1.项目结构

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

    解释:本deom中的common模块主要是用来存放实体类(本应放在pojo模块,偷个懒),自定义异常处理的类,还有自定义返回结果(都是比较基础的东西,不在赘述);

    3、uaa模块(satoken模块)

    1、项目结构

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

    解释:本模块主要用用来做用户的登录以及登出的,我们一步一步的带大家走;

    2、思维讲解和逻辑解析
    1、引入模块依赖pom.xml
     
            
            
                com.cqie
                common
                0.0.1-SNAPSHOT
            
            
            
                cn.dev33
                sa-token-spring-boot-starter
                1.37.0
            
            
            
                cn.dev33
                sa-token-jwt
                1.37.0
            
            
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
            
            
            
                cn.dev33
                sa-token-redis-jackson
                1.37.0
            
            
            
                org.apache.commons
                commons-pool2
            
        
    2、编写application.yml与application-dev.yml文件
    #application.yml
    server:
      port: 3080
    spring:
      application:
        name: saTokenService #微服务名称
      profiles:
        active: dev #代表是开发环境
      cloud:
        #nacos相关基础配置
        nacos:
          server-addr: xxxxx #自己的nacos端口
      #redis相关基础配置
      redis:
        port: 6379
        host: 127.0.0.1
      # 数据库配置
      datasource:
        druid:
          driver-class-name: ${factory.datasource.driver-class-name}
          url: jdbc:mysql://${factory.datasource.host}:${factory.datasource.port}/${factory.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
          username: ${factory.datasource.username}
          password: ${factory.datasource.password}
    #sa-token相关配置
    sa-token:
      # token 名称(同时也是 cookie 名称)
      token-name: token
      # token 有效期(单位:秒) 默认30天,-1 代表永久有效
      timeout: 72000
      # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
      is-concurrent: true
      # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
      is-share: false
      # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
      token-style: simple-uuid
      #  # 是否从cookie中读取token
      is-read-cookie: false
      #  # 是否从head中读取token
      is-read-header: true
      #是否输出操作日志
      is-log: true
      #是否开启sa-token的启动动画
      is-print: off #关闭控制台启动动画
      # jwt秘钥
      jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
    #mybatis-plus的相关依赖
    mybatis-plus:
      global-config:
        banner: off #关闭mybatis-plus的启动动画
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
      mapper-locations: classpath:mapper/*.xml
    
    #application-dev.yml
    factory:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        host: localhost
        port: 3306
        database: sa_token_test
        username: root
        password: 123456

    关于为什么会使用的dev呢,因为我是从上个项目cv过来的哈哈哈。

    注意在本模块的启动类上加一个@EnableWebMvc

    3、全局异常拦截器
    @RestControllerAdvice
    @Slf4j
    public class GlobalExceptionHandler {
        /**
         * 全局运行时异常拦截器
         *
         * @param e
         * @return
         */
        @ExceptionHandler(BaseException.class)
        public Result ExceptionHandler(BaseException e) {
            log.info("异常信息为:{}", e.getMessage());
            return Result.error(e.getMessage());
        }
        /**
         * 全局异常拦截器
         *
         * @param e
         * @return
         */
        @ExceptionHandler(Exception.class)
        public Result ExceptionHandler(Exception e) {
            log.info("异常信息为:{}", e.getMessage());
            return Result.error(e.getMessage());
        }
    }
    

    这个不赘述;

    4、SatokenConfig(uaa模块)
    @Configuration
    @Slf4j
    public class SaTokenConfig implements WebMvcConfigurer {
        /**
         * jwt生成
         * @return
         */
        @Bean
        public StpLogic stpLogicJWT(){
            return new StpLogicJwtForSimple();
        }
        /**
         * 配置所有需要被排除的路径
         *
         * @return
         */
        public List getList() {
            List list = new ArrayList<>();
            list.add("/favicon.ico");
            list.add("/error");
            list.add("/swagger-resources/**");
            list.add("/webjars/**");
            list.add("/v2/**");
            list.add("/doc.html");
            list.add("**/swagger-ui.html");
            return list;
        }
        /**
         * 注册Sa-token拦截器,打开注解鉴权功能
         *
         * @param registry
         */
        public void addInterceptors(InterceptorRegistry registry) {
            log.info("开始注册Sa-token拦截器............");
            // 注册 Sa-Token 拦截器,打开注解式鉴权功能
            registry.addInterceptor(new SaInterceptor(handle -> {
                //还可以分模块进行校验,不同模块需要不同的鉴权
                SaRouter.match("/user/**", "/user/doLogin", r -> StpUtil.checkRoleOr("user", "admin"));
            })).addPathPatterns("/**").excludePathPatterns(getList());
        }
        /**
         * 跨域配置
         *
         * @param registry
         */
        public void addCorsMappings(CorsRegistry registry) {
            log.info("开始进行跨域拦截器配置.......");
            registry.addMapping("/**")
                    .allowedHeaders("*")
                    .allowedMethods("*")
                    .maxAge(1800)
                    .allowedOrigins("*");
        }
    }
    • 首先关于jwt的生成,那个东西直接在官方文档抄的一模一样,在依赖中我也做了明显的标识,在yml里面我也有写到satoken整合使用到了jwt,关于为什么用呢,我单纯觉得长更安全(狗头);
    • 此处使用到这个sa-token的拦截器和官方文档也是一样的,不过排除了登录接口,其他接口进来需要校验是否有这两个角色权限。(比较重要的一点就是,只有访问当前模块下的接口才会走这个拦截器来校验,走其他模块的不会校验,要在其他模块下自己校验);
    • 跨域我也不细说了,放行这里排除的是swagger的相关路径。
    5、controller代码
    @RestController
    @RequestMapping("/user/")
    @Api(tags = "用户信息管理")
    @Slf4j
    public class UserController {
        @Resource
        private UserService userService;
        /**
         * 登录
         *
         * @param username
         * @param password
         * @return
         */
        @GetMapping("doLogin")
        public Result doLogin(String username, String password) {
            //此处仅做模拟,真实情况,要查询数据库
            if (userService.doLogin(username, password)) {
                return Result.success("登陆成功");
            }
            return Result.error("登陆失败");
        }
        /**
         * 查询登录页状态
         *
         * @return
         */
        //@SaCheckLogin //查看是否已经登录,已经登录才可以访问这个接口
        //只要满足了里面的一个文件条件就行
        @SaCheckOr(
                login = @SaCheckLogin,
                role = @SaCheckRole("admin"),
                permission = @SaCheckPermission("user.get"),
                safe = @SaCheckSafe("update.password"),
                basic = @SaCheckBasic(account = "sa:123456"),
                disable = @SaCheckDisable("submit-orders")
        )
        @ApiOperation("是否登录")
        @GetMapping("isLogin")
        public Result isLogin() {
            return Result.success("当前的登录状态为", StpUtil.isLogin());
        }
        /**
         * 查询token信息
         *
         * @return
         */
        @SaCheckRole("user")
        @GetMapping("tokenInfo")
        public Result tokenInfo() {
            return Result.success(StpUtil.getTokenInfo());
        }
        /**
         * 获取角色权限集合
         * @return
         */
        @SaCheckPermission(value = "qwewe",orRole = "admin") //双重or,表示权限为qweqwe或者角色权限为admin
        @GetMapping("getPermissionList")
        public Result getPermissionList() {
            List booleanList = new ArrayList<>();
            //获取该角色权限集合
            List permissionList = StpUtil.getPermissionList();
            log.info("全部权限信息为:{}",permissionList);
            //判断当前账号是否含有指定权限,返回true或者false
            boolean b = StpUtil.hasPermission("user.add");
            booleanList.add(b);
            // 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
            StpUtil.checkPermission("user.add");
            // 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
            StpUtil.checkPermissionAnd("user.add", "user.get");
            // 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
            StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
            boolean hassed = StpUtil.hasPermission("art.add");
            booleanList.add(hassed);
            return Result.success(booleanList);
        }
        @GetMapping("logout")
        public Result logout(Long id){
            //强制下线
            StpUtil.logout(id);                                      // 强制指定账号注销下线
    //        StpUtil.logout(10001, "PC");              // 强制指定账号指定端注销下线
    //        StpUtil.logoutByTokenValue("token");      // 强制指定 Token 注销下线
            //踢人下线
    //        StpUtil.kickout(10001);                    // 将指定账号踢下线
    //        StpUtil.kickout(10001, "PC");              // 将指定账号指定端踢下线
    //        StpUtil.kickoutByTokenValue("token");      // 将指定 Token 踢下线
            return Result.success();
        }
    }

    上面的使用的StpUtil全是在官方文档有的,不赘述。

    6、serviceImpl代码
    /**
     * 

    * 服务实现类 *

    * * @author 小飞 * @since 2024-04-16 */ @Service public class UserServiceImpl extends ServiceImpl implements UserService { @Resource private UserMapper userMapper; /** * 登录 * @param username * @param password * @return */ @Override public boolean doLogin(String username, String password) { User user = userMapper.selectOne(new LambdaQueryWrapper().eq(User::getUsername, username)); if (user == null || !Objects.equals(user.getPassword(), password)){ return false; } StpUtil.login(user.getId()); return true; } }

    基础登录很简单,不多解释

    7、然后就是角色权限的代码了
    @Component
    public class StpInterfaceImpl implements StpInterface {
        /**
         * 返回权限集合
         * @param o
         * @param s
         * @return
         */
        public List getPermissionList(Object o, String s) {
            List list = new ArrayList<>();
            list.add("user.add");
            list.add("user.update");
            list.add("user.get");
            list.add("art.*");
            return list;
        }
        /**
         * 返回一个账号所拥有的角色标识集合
         * @param o
         * @param s
         * @return
         */
        public List getRoleList(Object o, String s) {
            // 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询角色
            List list = new ArrayList();
            list.add("admin");
            list.add("super-admin");
            return list;
        }
    }

    其他地方的代码就不贴了。。。几乎没写。。

    4、gateway模块

    1、项目结构

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

    2、思维讲解和逻辑分析
    1、引入模块依赖
     
            
                com.cqie
                common
                0.0.1-SNAPSHOT
            
            
            
                cn.dev33
                sa-token-reactor-spring-boot-starter
                1.37.0
            
            
            
                org.springframework.cloud
                spring-cloud-starter-gateway
            
            
                org.springframework.boot
                spring-boot-starter-freemarker
            
            
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
            
            
            
                cn.dev33
                sa-token-jwt
                1.37.0
            
            
            
                cn.dev33
                sa-token-redis-jackson
                1.37.0
            
            
            
                org.apache.commons
                commons-pool2
            
            
            
                org.springframework.cloud
                spring-cloud-loadbalancer
            
        
    2、编写application.yml与application-dev.yml文件
    #application.yml
    server:
      port: 3070
    spring:
      profiles:
        active: dev # 开发环境
      application:
        name: gatewayService
      main:
        banner-mode: off
      datasource:
        druid:
          driver-class-name: ${factory.datasource.driver-class-name}
          url: jdbc:mysql://${factory.datasource.host}:${factory.datasource.port}/${factory.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
          username: ${factory.datasource.username}
          password: ${factory.datasource.password}
      redis:
        port: 6379
        host: 127.0.0.1
      cloud:
        nacos:
          server-addr: 192.168.87.135:8848
        gateway:
          routes:
            - id: bookInfo-service  #唯一标识,必须唯一
              uri: lb://bookInfoService #路由的目标地址
              predicates: #路由断言,判断请求是否符合规则
                - Path=/bookInfo/** #判断路径是否以这个路径开头
            - id: saToken-service  #唯一标识,必须唯一
              uri: lb://saTokenService #路由的目标地址
              predicates: #路由断言,判断请求是否符合规则
                - Path=/user/** #判断路径是否以这个路径开头
    #sa-token相关配置
    sa-token:
      # token 名称(同时也是 cookie 名称)
      token-name: token
      # token 有效期(单位:秒) 默认30天,-1 代表永久有效
      timeout: 72000
      # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
      is-concurrent: true
      # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
      is-share: false
      # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
      token-style: simple-uuid
      #  # 是否从cookie中读取token
      is-read-cookie: false
      #  # 是否从head中读取token
      is-read-header: true
      #  #能否写入到heard中
      #  is-write-header: true
      #是否输出操作日志
      is-log: true
      #是否开启sa-token的启动动画
      is-print: off #关闭控制台启动动画
      # jwt秘钥
      jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
    #application-dev.yml
    factory:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        host: localhost
        port: 3306
        database: sa_token_test
        username: root
        password: 123456

    3、saTokenConfig(gateway模块)

    @Component
    @RequiredArgsConstructor
    public class SaTokenConfig {
        /**
         * 注册 [Sa-Token全局过滤器]
         */
        @Bean
        public SaReactorFilter getSaReactorFilter() {
            return new SaReactorFilter()
                    // 指定 [拦截路由]
                    .addInclude("/**")    /* 拦截所有path */
                    // 指定 [放行路由]
                    .addExclude("/favicon.ico", "/user/doLogin")
                    // 指定[认证函数]: 每次请求执行
                    .setAuth(obj -> {
                        System.out.println("---------- sa全局认证");
                        SaRouter.match("/user/**").check(r -> StpUtil.checkLogin());
                    })
                    // 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
                    .setError(e -> {
                        System.out.println("---------- sa全局异常 ");
                        return SaResult.error(e.getMessage());
                    })
                    ;
        }
    }

    好了到目前位置全部的有效代码都配置完了;

    四、运行以及提出问题

    启动项目:springCloud整合gateway、satoken认证鉴权(包括问题解释)

    服务注册成功!

    1、运行测试(我用的apifox)

    我们先测试登录接口:springCloud整合gateway、satoken认证鉴权(包括问题解释)

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

     登录成功,我这里没有把token返回给前端,哈哈,只能复制一下到header了。。。

    看看redis里面呢

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

     也生成进去了

    稍微提一嘴,在satoken里面集成了satoken自带的redis依赖后,只要登录了就会在底层生成token存入到redis中,不用我们自己写存取的过程

     好,那就是登录成功了哈

    我们在测试一下UserController里面的isLogin方法:

    把token放到请求中:springCloud整合gateway、satoken认证鉴权(包括问题解释)

     开始测试:

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

     好!重要的来了!

    大家看,这里的登录状态为true,意味着我们是直接由于路由调用到这方法的,可是,如果大家有细心看gteway层的saTokenConfig的代码的话就会发现一个问题

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

     我们在satoken的过滤器里面只是放行了登录的端口,没有放行查询登录信息的端口,这里的我们做了拦截,要对其他端口进行satoken校验登录,那么想得比较多的朋友就有问题了,我们只是在satoken模块进行登录了,怎么到gateway模块也能进行查询登录呢?这里就是最容易让人产生疑惑的地方。接下来我来告诉大家原因:

    因为StpUtil.checkLogin()底层会根据生成token的方式去查询redis中是否有我们从header中传过来的token是否相等,并且把里面的角色id拿出来对比,是否一样,如果一样的话就验证通过,如果不一样的话就不行;所以我们在gateway模块也集成了在satoken模块的喜多相同的配置信息,方便直接查找,不用手动;后续如果想要校验其他的或者鉴权也可以直接使用StpUtil的内部的方法,只要token传对了就行;

    那么我们接着走:修改gateway的satokenConfig的springCloud整合gateway、satoken认证鉴权(包括问题解释)

     把验证登录修改为鉴权为有没有admin这个角色权限,重启gateway小模块测试一下:

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

    失败咯,没有这个角色权限呢。。。又有细心的同学发现了,我们在登陆的时候不是给了权限的吗,怎么不能像登录那样用token查到呢?

    springCloud整合gateway、satoken认证鉴权(包括问题解释)

    漏漏漏,当然不行,你这个给用户权限是在satoken模块,鉴权却是在gateway模块,token只携带了redis里面存的身份验证的信息,没有鉴权的信息,那么这个时候就可以把这个类复制一遍拿到gateway里面的config包下,重新给权限(重新给是有点傻的)可以让satoken暴露出一个接口是查询用户权限信息的然后返回,我们在gateway重写这个类之后,在里面用openFeign远程调用一下这个接口,然后给进去,也不是特别难得方法,大家可以下去试试

     大家可以自己试试登出的方法,我这里就不演示了。。。。

    五、最后

    大家看到这里应该也大差不差的懂了一些了吧,我说一点自己的想法,上面的那个satoken登录之后写入reids,本来我是不想用的,我自己集成reids写的一个生成和存取token的类,但是我发现那个需要自己手动判断登录,而且不能再其他地方使用鉴权,我就放弃了,改用他自己的这个了,用下来感觉也不错。如果大家还有什么问题可以直接评论区问我哟,虽然我也是小白。。一起加油,代码包在下方gitee,大家自取喔。(如有错误请指正,谢谢啦)

    sa_token_demo

转载请注明来自码农世界,本文标题:《springCloud整合gateway、satoken认证鉴权(包括问题解释)》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,94人围观)参与讨论

还没有评论,来说两句吧...

Top