草庐IT

SpringCloud整合Sa-Token登录认证+Gateway网关拦截

Yang疯狂打码中 2023-04-04 原文

Sa-Token介绍Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证权限认证单点登录OAuth2.0分布式Session会话微服务网关鉴权 等一系列权限相关问题

本文章框架使用:

SpringCloudAlibaba、SpringBoot2.1.13、sa-token1.30.0、redis

服务架构

 开始

一、首先配置网关服务

1、pom.xml

      <!-- Sa-Token 权限认证(Reactor响应式集成) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
            <version>1.30.0</version>
        </dependency>
        <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>1.30.0</version>
        </dependency>
        <!--GateWay 网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

2、bootstrap.yml引入sa-token配置


# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: satoken
  # 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

3、新建类SaTokenConfigure,实现网关拦截

package com.frontop.meta.config;

import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.frontop.meta.util.ResultJsonUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.server.ServerWebExchange;

/**
 * @author YangBoss
 * @title: SaTokenConfigure
 * @projectName meta
 * @description: TODO
 * @date 2022/8/18 10:12
 */
@Configuration
public class SaTokenConfigure {
    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")
                // 开放地址
                .addExclude("/favicon.ico")
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
                    SaRouter.match("/**", "/meta-auth/phoneLogin", r -> StpUtil.checkLogin());
                    // 角色认证 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证
                    SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
                    // 权限认证 -- 不同模块, 校验不同权限
                    SaRouter.match("/meta-system/**", r -> StpUtil.checkPermission("system-no"));
                    SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
                    SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
                    SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    // 设置错误返回格式为JSON
                    ServerWebExchange exchange = SaReactorSyncHolder.getContext();
                    exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
//                    return new ResultJsonUtil().fail(e.getMessage());
                    return SaResult.error(e.getMessage());
                })
                .setBeforeAuth(obj -> {
                    // ---------- 设置跨域响应头 ----------
                    SaHolder.getResponse()
                            // 允许指定域访问跨域资源
                            .setHeader("Access-Control-Allow-Origin", "*")
                            // 允许所有请求方式
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            // 有效时间
                            .setHeader("Access-Control-Max-Age", "3600")
                            // 允许的header参数
                            .setHeader("Access-Control-Allow-Headers", "*");

                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
                            .back();
                });
    }


}

4、新建全局异常处理类GlobalException

package com.frontop.meta.config;

import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.frontop.meta.constant.ResponseCodeConstant;
import com.frontop.meta.constant.ResponseMessageConstant;
import com.frontop.meta.util.ResultJsonUtil;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author YangBoss
 * @title: GlobalException
 * @projectName meta
 * @description: 拦截全局异常类
 * @date 2022/8/19 15:39
 */
public class GlobalException {
    // 全局异常拦截(拦截项目中的所有异常)
    @ResponseBody
    @ExceptionHandler
    public ResultJsonUtil<Object> handlerException(Exception e) {

        // 打印堆栈,以供调试
//        System.out.println("全局异常---------------");
        e.printStackTrace();

        // 不同异常返回不同状态码
        ResultJsonUtil<Object> re = null;
        if (e instanceof NotLoginException) {	// 如果是未登录异常
            NotLoginException ee = (NotLoginException) e;
            re =  new ResultJsonUtil().customized(
                    ResponseCodeConstant.OAUTH_TOKEN_FAILURE,
                    ResponseMessageConstant.OAUTH_TOKEN_MISSING,
                    null
            );
        }
        else if(e instanceof NotRoleException) {		// 如果是角色异常
            NotRoleException ee = (NotRoleException) e;
            re =  new ResultJsonUtil().customized(
                    ResponseCodeConstant.OAUTH_TOKEN_DENIED,
                    "无此角色:" + ee.getRole(),
                    null
            );
        }
        else if(e instanceof NotPermissionException) {	// 如果是权限异常
            NotPermissionException ee = (NotPermissionException) e;
            re =  new ResultJsonUtil().customized(
                    ResponseCodeConstant.OAUTH_TOKEN_DENIED,
                    "无此角色:" + ee.getCode(),
                    null
            );
        }
        else if(e instanceof DisableLoginException) {	// 如果是被封禁异常
            DisableLoginException ee = (DisableLoginException) e;
            re =  new ResultJsonUtil().customized(
                    ResponseCodeConstant.USER_LOCK,
                    "账号被封禁:" + ee.getDisableTime() + "秒后解封",
                    null
            );
        }
        else {	// 普通异常, 输出:500 + 异常信息
            re =  new ResultJsonUtil().fail(e.getMessage());
        }

        // 返回给前端
        return re;
    }
}

5、新建类StpInterfaceImpl,实现获取当前账号权限角色集合

package com.frontop.meta.config;

import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @author YangBoss
 * @title: StpInterfaceImpl
 * @projectName meta
 * @description: TODO
 * @date 2022/8/18 10:26
 */
@Component
public class StpInterfaceImpl implements StpInterface {
    /**
    *当前全部是模拟数据,真实情况使用根据loginId动态查询对应角色和权限
    */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表
        List<String> strs = new ArrayList<>();
        strs.add("system");
        return strs;
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的角色列表
        List<String> strs = new ArrayList<>();
        strs.add("admin");
        return strs;
    }

}

二、配置授权服务

1、pom.xml

 <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.30.0</version>
        </dependency>


        <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>1.30.0</version>
        </dependency>

2、bootstarp.yml引入sa-token配置


# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: satoken
  # 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

3、编写一个简单的登录

package com.frontop.meta.controller;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.frontop.meta.util.ResultJsonUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author YangBoss
 * @title: UserLoginController
 * @projectName meta
 * @description: TODO
 * @date 2022/8/19 14:44
 */
@RestController
@Api(tags = "用户授权登录")
public class UserLoginController {

    @ApiOperation(value = "手机+密码登录")
    @PostMapping("/phoneLogin")
    public ResultJsonUtil<Object> getAwardCount(String phone,String password) {
        
        if(phone.equals("18874288923") && password.equals("123")){
            StpUtil.login(1001,"PC");
            return new ResultJsonUtil().success(StpUtil.getTokenInfo());
        }
        return new ResultJsonUtil().fail("手机号或密码错误");
    }


}

三、测试效果

1、启动网关服务和授权服务后调用登录接口

 redis中

 到这里简单的登录就完成了

2、在system业务服务中简单配置一个测试接口

 system业务服务中也需要引入sa-token,bootsrap.yml配置都是一样的

<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:http://sa-token.dev33.cn/ -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-reactor-spring-boot-starter</artifactId>
            <version>1.30.0</version>
        </dependency>
        <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-dao-redis-jackson</artifactId>
            <version>1.30.0</version>
        </dependency>

3、不携带token值访问接口

 4、携带token访问 

这里报无权限的原因就是网关实现了拦截,在上面配置中网关配置了meta-system路由的权限必须使用system-no

而我们在添加权限集合时候没有该权限所以被拦截 

角色拦截配置也是类似

四、使用注解拦截

1、如果想使用注解拦截,只能写在业务服务的接口层

2、首先要在业务服务中开启注解拦截配置

package com.frontop.meta;

import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author YangBoss
 * @title: SaTokenConfigure
 * @projectName meta
 * @description: TODO
 * @date 2022/9/7 10:53
 */
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册Sa-Token的注解拦截器,打开注解式鉴权功能
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
        registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
    }
}

3、也需要配置获取角色和权限的集合类

package com.frontop.meta;

import cn.dev33.satoken.stp.StpInterface;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @author YangBoss
 * @title: StpInterfaceImpl
 * @projectName meta
 * @description: TODO
 * @date 2022/8/18 10:26
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表
        List<String> strs = new ArrayList<>();
        strs.add("system-no");
        return strs;
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的角色列表
        List<String> strs = new ArrayList<>();
        strs.add("admin");
        return strs;
    }

}

4、接口配置注解拦截

@RestController
@RequestMapping("/test")
@Api(tags = "测试")
public class TestContorller {

    @ApiOperation(value = "请求汇总",consumes = "application/json;charset=UTF-8")
    @RequestMapping(value = "/apiGather", method = RequestMethod.POST)
    @SaCheckRole("super-admin2")//必须拥有该角色可访问
    @SaCheckPermission("system-no")//必须拥有该权限可访问
    public ResultJsonUtil<Object> apiGather(){
        return new ResultJsonUtil().success("111");
    }

}

5、测试效果

网关跨域的问题

解决:

在网关服务中创建类Cors

package com.geo.gateway.config;

import org.springframework.web.cors.reactive.CorsWebFilter;

import org.springframework.context.annotation.Bean;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
 * @author YangBoss
 * @title: CorsConfig
 * @projectName frontop-geo-cloud
 * @description: TODO
 * @date 2023/1/30 17:57
 */
public class CorsConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource (new PathPatternParser());
        CorsConfiguration corsConfig = new CorsConfiguration();
        // 允许所有请求方法
        corsConfig.addAllowedMethod ("*");
        // 允许所有域,当请求头
        corsConfig.addAllowedOrigin ("*");
        // 允许全部请求头
        corsConfig.addAllowedHeader ("*");
        // 允许携带 Cookie 等用户凭证
        corsConfig.setAllowCredentials (true);
        // 允许全部请求路径
        source.registerCorsConfiguration ("/**", corsConfig);
        return new CorsWebFilter (source);
    }
}

git地址:https://gitee.com/yangjial/meta.git

代码在frontop的分支,记得切换

有关SpringCloud整合Sa-Token登录认证+Gateway网关拦截的更多相关文章

  1. ruby-on-rails - Rails 中的 NoMethodError::MailersController#preview undefined method `activation_token=' for nil:NilClass - 2

    似乎无法为此找到有效的答案。我正在阅读Rails教程的第10章第10.1.2节,但似乎无法使邮件程序预览正常工作。我发现处理错误的所有答案都与教程的不同部分相关,我假设我犯的错误正盯着我的脸。我已经完成并将教程中的代码复制/粘贴到相关文件中,但到目前为止,我还看不出我输入的内容与教程中的内容有什么区别。到目前为止,建议是在函数定义中添加或删除参数user,但这并没有解决问题。触发错误的url是http://localhost:3000/rails/mailers/user_mailer/account_activation.http://localhost:3000/rails/mai

  2. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  3. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  4. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  5. ruby-on-rails - 如何找出拦截 'method_missing' 的内容 - 2

    使用Ruby1.8.6/Rails2.3.2我注意到在我的任何ActiveRecord模型类上调用的任何方法都返回nil而不是NoMethodError。除了烦人之外,这还破坏了动态查找器(find_by_name、find_by_id等),因为即使存在记录,它们也总是返回nil。不从ActiveRecord::Base派生的标准类不受影响。有没有办法追踪在ActiveRecord::Base之前拦截method_missing的是什么?更新:切换到1.8.7后,我发现(感谢@MichaelKohl)will_paginate插件首先处理method_missing。但是will_pa

  6. ruby-on-rails - 设计通过 reset_password_token 获取用户 - 2

    我正在尝试创建密码规则来设计可恢复的密码更改。我通过passwords_controller.rb做了一个父类(superclass),但我需要在应用规则之前检查用户角色,但我所拥有的只是reset_password_token。 最佳答案 假设您的模型是用户:User.with_reset_password_token(your_token_here)Source 关于ruby-on-rails-设计通过reset_password_token获取用户,我们在StackOverflow

  7. ruby - token 认证 - 2

    简单代码require'net/http'url=URI.parse('getjson/otherdatahere[link]')req=Net::HTTP::Get.new(url.to_s)res=Net::HTTP.start(url.host,url.port){|http|http.request(req)}putsres.body只是想知道如何在phpcURL中放置身份验证token,我是这样做的    curl_setopt($ch,CURLOPT_HTTPHEADER,array('Authorization:Bearerxxx'));//Bearertokenfora

  8. ruby - HTTParty 摘要认证 - 2

    谁能提供一个使用HTTParty和digestauth的例子?我在网上找不到例子,希望有人能提供一些帮助。谢谢。 最佳答案 您可以在定义类时使用digest_auth方法设置用户名和密码classFooincludeHTTPartydigest_auth'username','password'end 关于ruby-HTTParty摘要认证,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questi

  9. ruby - 使用 Ruby 和 Mechanize 登录网站 - 2

    我需要从站点抓取数据,但它需要我先登录。我一直在使用hpricot成功地抓取其他网站,但我是使用mechanize的新手,我真的对如何使用它感到困惑。我看到这个例子经常被引用:require'rubygems'require'mechanize'a=Mechanize.newa.get('http://rubyforge.org/')do|page|#Clicktheloginlinklogin_page=a.click(page.link_with(:text=>/LogIn/))#Submittheloginformmy_page=login_page.form_with(:act

  10. ruby-on-rails - 访问授权和访问 token 之间的区别 - 2

    Doorkeeper中Token和Grant的区别我搞不清楚。Doorkeeper在哪个时刻创建访问授权,何时创建访问token?文档似乎对此什么也没说,现在我正在阅读代码,但不是十几行。 最佳答案 我还建议阅读documentationofoauth2据我了解,Doorkeeper也是基于该文档中描述的协议(protocol)。在doorkeeper中,你会先获得accessgrant,然后是accesstoken。访问授权通常只存在很短的时间(doorkeeper中的默认值为10分钟)。您将通过向api-url/oauth/au

随机推荐