微服务架构整合Sa-Token实现网关鉴权及鉴权服务

文章讲述了如何在SpringCloudAlibaba微服务架构中,利用Sa-Token实现网关鉴权,包括依赖引入、配置和实际应用案例。
摘要由CSDN通过智能技术生成

微服务架构整合Sa-Token实现网关鉴权及鉴权服务

这个项目是自己做的一个练手项目,使用SpringCloudAlibaba微服务架构,在做鉴权模块的时候想起来之前在网上看见的Sa-Token项目,被称为国产鉴权之光故查阅了他们的文档实现了一套鉴权服务
一整套使用下来发现SaToken还是比较迎合国内程序员的编码习惯,与SpringSecurity那一套繁琐的过滤链责任链模式不同,SaToken主要还是依靠调方法类中的方法来实现登入,登出,鉴权等一系列流程

项目架构

image.png

image.png

代码实现

引入依赖,由于是微服务项目,我们用户信息必须存储在Redis上,SaToken已经为我们提供了SaToken整合Redis的依赖,无需我们手动实现代码 ,注意由于GateWay网关是基于WebFlux实现的与我们平常的MVC项目引入的依赖不同,SaToken的官方文档中也已经提及

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

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

网关中的鉴权拦截器SaToken也已经为我们留下拓展点,我们只需要实现我们自己的业务逻辑即可 代码如下

package com.titi.apigateway.config;

import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.titi.titicommon.enums.AppHttpCodeEnum;
import com.titi.titicommon.result.ResponseResult;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.stream.Collectors;

@Configuration
public class SaTokenFilterConfiguration {
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")    /* 拦截全部path */
                // 开放地址
                .addExclude("/favicon.ico")
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,并排除/auth/** 用于开放登录
                    SaRouter.match("/**", "/auth/**", r -> StpUtil.checkLogin());
                    // 权限认证 -- 不同模块, 校验不同权限
                    SaRouter.match("/passenger/**", r -> StpUtil.checkPermission("passenger"));
                    SaRouter.match("/driver/**", r -> StpUtil.checkPermission("driver"));
                    SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
//                  SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
// 更多匹配 ...  */
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    return SaResult.error(e.getMessage());
               });
    }

    /**
     * 由于网关没有引入springMVC依赖,所以使用feign的时候需要手动装配messageConverters
     * @param converters
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }
}

SaToken为了兼容不同业务场景,将获取权限与获取当前用户角色的方法也开放给我们使用者,是需要实现对应的接口再注册到Spring中即可
这里的逻辑大家根据自己的系统实现即可,一般思路是从缓存或者数据库中的表获取对应userId下的权限或者角色信息
这里获取到的权限大家Debug源码的时候可以看到请求经过SaToken的鉴权过滤器的时候底层会调用这两个方法来获取用户权限来鉴权

package com.titi.apigateway.config;

import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import com.titi.feign.client.UserServiceClient;
import com.titi.titicommon.DTO.UserPermissionDto;
import com.titi.titicommon.result.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 自定义权限验证接口扩展 
 */
@Component
public class StpInterfaceImpl implements StpInterface {

    @Resource
    private UserServiceClient userServiceClient;

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表
        //调用远程服务获取权限列表
        ResponseResult<List<UserPermissionDto>> userPermission = userServiceClient.getUserPermission(Long.parseLong(loginId.toString()));
        List<UserPermissionDto> permissList = userPermission.getData();
        if (CollUtil.isEmpty(permissList)){
            return null;
        }
        return permissList.stream().map(UserPermissionDto::getPermission).collect(Collectors.toList());
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的角色列表
        return null;
    }

}

授权登入部分

这里我抽象除了Auth服务用于多种身份下的角色登入,因为我的系统包括乘客端,司机端,管理员端,正常来说为了系统的可拓展性我应该再加一张角色表来做权限校验,但是由于是练手项目就怎么简单怎么来了,我只是用了单权限表来做
为了保证Auth服务尽量轻量级大部分的业务逻辑我都写在User服务中使用Feign进行远程调用
从代码中可以看出来在登入逻辑校验通过后我们只需要将用户的userId交给StpUtil工具类SaToken就会帮我们做例如Token生成,Token时效性设置,Token同步Redis,生成存放Token的Cookie返回给前端,还是非常方便的

package com.titi.auth.controller;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.titi.feign.client.SMSServiceClient;
import com.titi.feign.client.UserServiceClient;
import com.titi.titicommon.DO.TitiUser;
import com.titi.titicommon.DTO.SMSUserVerifyRequest;
import com.titi.titicommon.DTO.TitiUserDto;
import com.titi.titicommon.DTO.UserVerifyRequest;
import com.titi.titicommon.constants.AuthConstants;
import com.titi.titicommon.constants.RegexConstants;
import com.titi.titicommon.enums.AppHttpCodeEnum;
import com.titi.titicommon.exception.BussinessException;
import com.titi.titicommon.result.ResponseResult;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

@RestController
public class PassengerUserController {

    @Autowired
    private UserServiceClient userServiceClient;

    @Autowired
    private SMSServiceClient smsServiceClient;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 登入注册二合一接口
     * @param titiUserDto
     * @param httpServletRequest
     * @return
     */
    @PostMapping("/doLogin")
    public ResponseResult doLoginOrRegister(@RequestBody TitiUserDto titiUserDto, HttpServletRequest httpServletRequest){
        //调用用户服务来进行登入或者注册登入
        ResponseResult login = userServiceClient.login(titiUserDto);
        if (!login.getCode().equals(200)){
            return ResponseResult.errorResult(login.getCode(),login.getErrorMessage());
        }
        Long userId = (Long) login.getData();
        StpUtil.login(userId);
        return ResponseResult.okResult(StpUtil.getTokenInfo());
    }

    @PostMapping("/logout")
    public ResponseResult doLogout(){
        if (StpUtil.isLogin()) {
            StpUtil.logout(StpUtil.getLoginId());
        }
        return ResponseResult.okResult(null);
    }

    @PostMapping("/sendVerifyCode")
    public ResponseResult sendVerifyCode(@RequestBody @Validated UserVerifyRequest userVerifyRequest, HttpServletRequest httpServletRequest){
        SMSUserVerifyRequest smsUserVerifyRequest = new SMSUserVerifyRequest();
        BeanUtils.copyProperties(userVerifyRequest,smsUserVerifyRequest);
        smsUserVerifyRequest.setIp(httpServletRequest.getRemoteAddr());
        String verifyTypeKey = AuthConstants.verifyMap.get(userVerifyRequest.getVerifyType());
        if (StrUtil.isBlank(verifyTypeKey)){
            throw new BussinessException(AppHttpCodeEnum.PARAM_INVALID);
        }
        smsUserVerifyRequest.setVerifyType(verifyTypeKey);
        //60s内只能接收一次验证码,使用Redis加锁来实现
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(verifyTypeKey + userVerifyRequest.getPhone()))) {
            throw new BussinessException(AppHttpCodeEnum.SMS_TIME_LIMIT);
        }
        //调用短信服务来发送信息
        return smsServiceClient.sendVerifyCode(smsUserVerifyRequest);
    }
}

GateWay重写转发请求携带Token转发给服务

有些业务场景中,我们在GateWay网关鉴权完毕分发请求给服务的时候,服务中的某些场景还需要使用用户信息,但是此时由于GateWay转发请求前端传来的Token已经丢失,这个时候我们可以在GateWay中增加一个全局过滤器在请求转发之前重写一次转发请求,在转发请求中携带上用户信息,这边我直接携带的就是userId了因为是内部服务调用也不用担心安全问题
`

package com.titi.apigateway.filter;

import cn.dev33.satoken.stp.StpUtil;
import com.titi.titicommon.constants.AuthConstants;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest newRequest = exchange
                .getRequest()
                .mutate()
                .header(AuthConstants.HTTP_LOGIN_HEADER, StpUtil.getLoginId(-1L).toString())
                .build();
        ServerWebExchange finalRequest = exchange.mutate().request(newRequest).build();
        return chain.filter(finalRequest);
    }
}

结语

SaToken的使用体验还是非常不错的,不愧是国产项目,简单易上手以及提供了很多拓展点供用户拓展,大家可以试一下呀

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
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轻松实现。这不仅提高了系统的安全性,同时也简化了系统的开发与维护工作。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LinkJii

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

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

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

打赏作者

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

抵扣说明:

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

余额充值