SpringCloud+Sa-Token网关统一鉴权

一、网关鉴权

1.1 引入依赖

<!-- Sa-Token 权限认证(Reactor响应式集成), 在线文档:https://sa-token.cc -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-reactor-spring-boot3-starter</artifactId>
    <version>1.39.0</version>
</dependency>

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

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

1.2 鉴权接口

package com.ozo.cloud.gateway.core;

import cn.dev33.satoken.stp.StpInterface;
import com.ozo.cloud.common.base.context.CurrentUserHolder;
import com.ozo.cloud.common.base.model.LoginUser;

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

/**
 * 权限实现类
 *
 * @author qiangesoft
 * @date 2024-07-08
 **/
public class SaPermissionImpl implements StpInterface {

    /**
     * 获取菜单权限列表
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        LoginUser loginUser = CurrentUserHolder.current();
        return new ArrayList<>(loginUser.getMenuPermission());
    }

    /**
     * 获取角色权限列表
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        LoginUser loginUser = CurrentUserHolder.current();
        return new ArrayList<>(loginUser.getRolePermission());
    }

}

1.3 过滤器配置

package com.ozo.cloud.gateway.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.json.JSONUtil;
import com.ozo.cloud.common.base.model.ResultVO;
import com.ozo.cloud.gateway.config.properties.SecurityProperties;
import com.ozo.cloud.gateway.core.SaPermissionImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * 鉴权配置
 *
 * @author qiangesoft
 * @date 2024-07-08
 **/
@EnableConfigurationProperties(SecurityProperties.class)
@Configuration
public class SaTokenConfig {

    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    public StpInterface stpInterface() {
        return new SaPermissionImpl();
    }

    /**
     * 注册过滤器
     */
    @Bean
    public SaReactorFilter saServletFilter() {
        SaReactorFilter saReactorFilter = new SaReactorFilter();

        // 前置函数:在每次认证函数之前执行
        saReactorFilter.setBeforeAuth(obj -> {
                    // 设置跨域响应头
                    SaHolder.getResponse()
                            // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            // .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                            .setHeader("Access-Control-Allow-Origin", "*")
                            .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
                            .setHeader("Access-Control-Max-Age", "3600")
                            .setHeader("Access-Control-Allow-Headers", "*");

                    // 如果是预检请求,则立即返回到前端
                    SaRouter.match(SaHttpMethod.OPTIONS)
                            .free(r -> {
                            })
                            .back();
                })

                // 异常处理
                .setError(e -> {
                    // 由于过滤器中抛出的异常不进入全局异常处理,所以此处统一转为JSON输出前端
                    SaResponse saResponse = SaHolder.getResponse();
                    saResponse.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON + ";charset=" + CharsetUtil.UTF_8);
                    return JSONUtil.parseObj(ResultVO.fail(e.getMessage()));
                });

        // 指定拦截路由
        saReactorFilter.addInclude("/**")
                // 设置鉴权的接口
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由
                    SaRouter.match("/**", r -> StpUtil.checkLogin());
                });

        // 指定开放路由
        List<String> excludePathPatterns = securityProperties.getExcludePathPatterns();
        if (CollUtil.isNotEmpty(excludePathPatterns)) {
            saReactorFilter.addExclude(excludePathPatterns.toArray(new String[0]));
        }

        return saReactorFilter;
    }

}

1.4 子服务添加Token

package com.ozo.cloud.gateway.filter;

import cn.dev33.satoken.same.SaSameUtil;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 全局过滤器,为请求添加 Same-Token
 *
 * @author qiangesoft
 * @date 2024-09-10
 */
@Component
public class ForwardAuthFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest newRequest = exchange
                .getRequest()
                .mutate()
                // 为请求追加 Same-Token 参数 
                .header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken())
                .build();
        ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
        return chain.filter(newExchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

二、子服务鉴权

2.1 引入依赖

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

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

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2.2 过滤器配置

package com.ozo.cloud.common.satoken.config;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.same.SaSameUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.json.JSONUtil;
import com.ozo.cloud.common.base.model.ResultVO;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Sa-Token 权限认证 配置类 
 *
 * @author qiangesoft
 * @date 2024-09-10
 */
@AutoConfiguration
public class SaTokenConfig implements WebMvcConfigurer {

    /**
     * 注解鉴权拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SaInterceptor())
                .addPathPatterns("/**");
    }

    /**
     * 注册 Sa-Token 全局过滤器
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()
                // 指定拦截路由
                .addInclude("/**")

                .setAuth(obj -> {
                    // 校验 Same-Token 身份凭证
                    SaSameUtil.checkCurrentRequestToken();
                })

                // 异常处理
                .setError(e -> {
                    // 由于过滤器中抛出的异常不进入全局异常处理,所以此处统一转为JSON输出前端
                    SaResponse saResponse = SaHolder.getResponse();
                    saResponse.setHeader(Header.CONTENT_TYPE.getValue(), ContentType.JSON + ";charset=" + CharsetUtil.UTF_8);
                    return JSONUtil.parseObj(ResultVO.fail(e.getMessage()));
                });
    }

    @Bean
    @ConditionalOnMissingBean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

2.3 拦截器

package com.ozo.cloud.common.satoken.interceptor;

import cn.dev33.satoken.same.SaSameUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;

/**
 * feign拦截器, 在feign请求发出之前,加入一些操作
 *
 * @author qiangesoft
 * @date 2024-09-10
 */
public class FeignInterceptor implements RequestInterceptor {

    /**
     * 为 Feign 的 RCP调用 添加请求头Same-Token
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header(SaSameUtil.SAME_TOKEN, SaSameUtil.getToken());

        // 如果希望被调用方有会话状态,此处就还需要将 satoken 添加到请求头中
        // requestTemplate.header(StpUtil.getTokenName(), StpUtil.getTokenValue());
    }

}

2.4 接口使用拦截器

package com.ozo.cloud.api.auth.feign;

import com.ozo.cloud.common.base.model.ResultVO;
import com.ozo.cloud.common.satoken.interceptor.FeignInterceptor;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * 授权服务调用
 *
 * @author qiangesoft
 * @date 2024-09-11
 */
@FeignClient(name = "ozo-cloud-auth", path = "/auth", configuration = FeignInterceptor.class)
public interface AuthFeignClient {

    @GetMapping("/userInfo")
    ResultVO<Object> getUserInfo();

}

SpringCloud是基于Spring框架的分布式系统开发框架,它提供了丰富的分布式系统解决方案。而Sa-Token是一个轻量级的Java权限认证与授权框架,提供了简单易用的权限控制功能。 在SpringCloud中整合Sa-Token可以为我们的分布式系统提供更加安全可靠的权限认证与授权机制。具体步骤如下: 1. 在SpringCloud的微服务架构中,将Sa-Token配置为一个独立的授权认证中心,可以独立部署,也方便对其进行管理和维护。 2. 在每个微服务中引入Sa-Token的依赖,通过配置Sa-Token的相关属性,实现微服务的用户登录与认证。同时,可以使用Sa-Token提供的注解进行权限控制,例如@RequiresPermissions注解可以对接口或方法进行权限校验。 3. 在微服务之间进行访问时,可以通过Sa-TokenToken验证机制,对调用方进行身份认证。因此,每个微服务需要验证并解析Token,以确保请求方的合法性。 4. Sa-Token提供了灵活的权限授权机制,可以根据业务需求配置不同的角色和权限管理。在SpringCloud中,我们可以根据微服务的不同职能划分角色,为每个角色分配相应的权限。通过角色与权限的配置,实现对不同微服务接口的精确控制。 通过将Sa-Token整合到SpringCloud中,我们可以实现整个系统的统一权限管理。无论是对用户的认证与授权,还是对接口的权限控制,都可以通过Sa-Token轻松实现。这不仅提高了系统的安全性,同时也简化了系统的开发与维护工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PG_强哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值