前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Gateway、Sa-Token、Nacos 认证/鉴权方案,yyds!

Spring Gateway、Sa-Token、Nacos 认证/鉴权方案,yyds!

作者头像
码猿技术专栏
发布2024-01-23 18:57:14
3900
发布2024-01-23 18:57:14
举报

大家好,我是不才陈某~

之前进行鉴权、授权都要写一大堆代码。如果使用像Spring Security这样的框架,又要花好多时间学习,拿过来一用,好多配置项也不知道是干嘛用的,又不想了解。要是不用Spring Security,token的生成、校验、刷新,权限的验证分配,又全要自己写,想想都头大。

Spring Security太重而且配置繁琐。自己实现所有的点必须又要顾及到,更是麻烦。

最近看到一个权限认证框架,真是够简单高效。这里分享一个使用Sa-Token的gateway鉴权demo。

需求分析

结构

认证

sa-token模块

我们首先编写sa-token模块进行token生成和权限分配。

在sa-token的session模式下生成token非常方便,只需要调用

代码语言:javascript
复制
StpUtil.login(Object id);     

就可以为账号生成 Token 凭证与 Session 会话了。

配置信息
代码语言:javascript
复制
server:
  # 端口
  port: 8081

spring:
  application:
    name: weishuang-account
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/weishuang_account?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
    username: root
    password: root
  # redis配置
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    # password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0


############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: weishuang-token
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: true
  # token风格
  token-style: uuid
  # 是否输出操作日志
  is-log: false
  # token前缀
  token-prefix: Bearer

在sa-token的配置中,我使用了token-name来指定token的名称,如果不指定那么就是默认的satoken。

使用token-prefix来指定token的前缀,这样前端在header里传入token的时候就要加上Bearer了(注意有个空格),建议和前端商量一下需不需要这个前缀,如果不使用,直接传token就好了。

现在调用接口时传入的格式就是

代码语言:javascript
复制
weishuang-token = Bearer token123456

sa-token的session模式需要redis来存储session,在微服务中,各个服务的session也需要redis来同步。

当然sa-token也支持jwt来生成无状态的token,这样就不需要在服务中引入redis了。本文使用session模式(jwt的刷新token等机制还要自己实现,session的刷新sa-token都帮我们做好了,使用默认的模式更加方便,而且功能更多)

我们来编写一个登录接口
User
代码语言:javascript
复制
@Data
public class User {

    /**
     * id
     */
    private String id;

    /**
     * 账号
     */
    private String userName;

    /**
     * 密码
     */
    private String password;

}
UserController
代码语言:javascript
复制
@RestController
@RequestMapping("/account/user/")
public class UserController {

    @Autowired
    private UserManager userManager;

    @PostMapping("doLogin")
    public SaResult doLogin(@RequestBody AccountUserLoginDTO req) {
        userManager.login(req);

        return SaResult.ok("登录成功");
    }
}
UserManager
代码语言:javascript
复制
@Component
public class UserManagerImpl implements UserManager {

    @Autowired
    private UserService userService;

    @Override
    public void login(AccountUserLoginDTO req) {
        //生成密码
        String password = PasswordUtil.generatePassword(req.getPassword());
        //调用数据库校验是否存在用户
        User user = userService.getOne(req.getUserName(), password);
        if (user == null) {
            throw new RuntimeException("账号或密码错误");
        }
        
        //为账号生成Token凭证与Session会话
        StpUtil.login(user.getId());
        //为该用户的session存储更多信息
        //这里为了方便直接把user实体存进去了,也包括了密码,自己实现时不建议这样做。
        StpUtil.getSession().set("USER_DATA", user);
    }

}
UserService
代码语言:javascript
复制
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    public User getOne(String username, String password){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username)
                .eq(User::getPassword,password);

        return userMapper.selectOne(queryWrapper);
    }
}

gateway模块

依赖
代码语言:javascript
复制
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!-- 引入gateway网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-web</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

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

    </dependencies>
配置
代码语言:javascript
复制
server:
  port: 9000
spring:
  application:
    name: weishuang-gateway
  cloud:
    loadbalancer:
      ribbon:
        enabled: false
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: localhost:8848
    gateway:
      routes:
        - id: account
          uri: lb://weishuang-account
          order: 1
          predicates:
            - Path=/account/**
  # redis配置
  redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    # password:
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
        # 连接池中的最大空闲连接
        max-idle: 10
        # 连接池中的最小空闲连接
        min-idle: 0

############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: weishuang-token
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  activity-timeout: -1
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: true
  # token风格
  token-style: uuid
  # 是否输出操作日志
  is-log: false
  # token前缀
  token-prefix: Bearer

同样的,在gateway中也需要配置sa-token和redis,注意和在account服务中配置的要一致,否则在redis中获取信息的时候找不到。

gateway我们也注册到nacos中。

拦截认证
代码语言:javascript
复制
package com.weishuang.gateway.gateway.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 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.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Configuration
public class SaTokenConfigure {

    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")    /* 拦截全部path */
                // 开放地址
                .addExclude("/favicon.ico")
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,并排除/account/user/doLogin用于开放登录
                    SaRouter.match("/**", "/account/user/doLogin", r -> StpUtil.checkLogin());

//                    // 权限认证 -- 不同模块, 校验不同权限
//                    SaRouter.match("/account/**", r -> StpUtil.checkRole("user"));
//                    SaRouter.match("/admin/**", r -> StpUtil.checkRole("admin"));
//                    SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
//                    SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));

                    // 更多匹配 ...  */
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                })
                ;
    }
}

只需要在gateway中添加一个全局过滤器进行鉴权操作就可以实现认证/鉴权操作了。

这里我们对**全部路径进行拦截,但不要忘记把我们的登录接口释放出来,允许访问。

到这里简单的认证操作就实现了。我们仅仅使用了sa-token的一个StpUtil.login(Object id)方法,其他事情sa-token都帮我们完成了,更无需复杂的配置和多到爆炸的Bean。

鉴权

有时候一个token认证并不能让我们区分用户能不能访问这个资源,使用那个菜单,我们需要更细粒度的鉴权。

在经典的RBAC模型里,用户会拥有多个角色,不同的角色又会有不同的权限。

这里我们使用五个表来表示用户、角色、权限之间的关系。

很显然,我们想判断用户有没有权限访问一个path,需要判断用户是否还有该权限。

在sa-token中想要实现这个功能,只需要实现StpInterface接口即可。

代码语言:javascript
复制
/**
 * 自定义权限验证接口扩展 
 */
@Component   
public class StpInterfaceImpl implements StpInterface {

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 返回此 loginId 拥有的权限列表 
        return ...;
    }

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

}

我们在gateway实现这个接口,为用户赋予权限,再进行权限校验,就可以精确到path了。

我们使用先从Redis中获取缓存数据,获取不到时走RPC调用account服务获取。

为了更方便的使用gateway调用account服务,我们使用nacos进行服务发现,用feign调用。

在account和gateway服务中配置nacos

配置nacos

在两个服务中加入nacos的配置

代码语言:javascript
复制
spring:
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: localhost:8848
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/weishuang_account?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
    username: root
    password: root

配置gateway

需要注意的是,gateway是基于WebFlux的一个响应式组件,HttpMessageConverters不会像Spring Mvc一样自动注入,需要我们手动配置。

代码语言:javascript
复制
package com.weishuang.gateway.gateway.config;

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 HttpMessageConvertersConfigure {
    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
    }
}

实现获取角色、权限接口

在account中实现通过用户获取角色、获取权限的接口

RoleController、PermissionController
代码语言:javascript
复制
@RestController
@RequestMapping("/account/role/")
public class RoleController {

    @Autowired
    private RoleManager roleManager;

    @PostMapping("/getRoles")
    public List<RoleDTO> getRoles(@RequestParam String userId) {
        return roleManager.getRoles(userId);
    }
}

@RestController
@RequestMapping("/account/permission/")
public class PermissionController {

    @Autowired
    private PermissionManager permissionManager;

    @PostMapping("/getPermissions")
    public List<PermissionDTO> getPermissions(@RequestParam String userId) {
        return permissionManager.getPermissions(userId);
    }

}
RoleManager
代码语言:javascript
复制
@Component
public class RoleManagerImpl implements RoleManager {

    @Autowired
    private RoleService roleService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private Role2RoleDTOCovert role2RoleDTOCovert;

    @Override
    public List<RoleDTO> getRoles(String userId) {
        List<UserRole> userRoles = userRoleService.getByUserId(userId);
        Set<String> roleIds = userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toSet());
        List<RoleDTO> roleDTOS = role2RoleDTOCovert.covertTargetList2SourceList(roleService.getByIds(roleIds));

        //服务不对外暴露,网关不传token到子服务,这里通过userId获取session,并设置角色。
        String tokenValue = StpUtil.getTokenValueByLoginId(userId);
  
        //为这个token在redis中设置角色,使网关获取更方便
        if(StringUtils.isNotEmpty(tokenValue)){
            if(CollectionUtils.isEmpty(roleDTOS)){
                StpUtil.getTokenSessionByToken(tokenValue).set("ROLES", "");
            }else{
                List<String> roleNames = roleDTOS.stream().map(RoleDTO::getRoleName).collect(Collectors.toList());
                StpUtil.getTokenSessionByToken(tokenValue).set("ROLES", ListUtil.list2String(roleNames));
            }
        }
        return roleDTOS;
    }
}
PermissionManager
代码语言:javascript
复制
@Component
public class PermissionManagerImpl implements PermissionManager {

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private RolePermService rolePermService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private Permission2PermissionDTOCovert permissionDTOCovert;

    @Override
    public List<PermissionDTO> getPermissions(String userId) {

        //获取用户的角色
        List<UserRole> roles = userRoleService.getByUserId(userId);
        if (CollectionUtils.isEmpty(roles)) {
            handleUserPermSession(userId, null);
        }

        Set<String> roleIds = roles.stream().map(UserRole::getRoleId).collect(Collectors.toSet());

        List<RolePerm> rolePerms = rolePermService.getByRoleIds(roleIds);

        if (CollectionUtils.isEmpty(rolePerms)) {
            handleUserPermSession(userId, null);
        }

        Set<String> permIds = rolePerms.stream().map(RolePerm::getPermId).collect(Collectors.toSet());
        List<PermissionDTO> perms = permissionDTOCovert.covertTargetList2SourceList(permissionService.getByIds(permIds));

        handleUserPermSession(userId, perms);
        return perms;
    }


    private void handleUserPermSession(String userId, List<PermissionDTO> perms) {
        //通过userId获取session,并设置权限
        String tokenValue = StpUtil.getTokenValueByLoginId(userId);

        if (StringUtils.isNotEmpty(tokenValue)) {
            //为了防止没有权限的用户多次进入到该接口,没权限的用户在redis中存入空字符串
            if (CollectionUtils.isEmpty(perms)) {
                StpUtil.getTokenSessionByToken(tokenValue).set("PERMS", "");
            } else {
                List<String> paths = perms.stream().map(PermissionDTO::getPath).collect(Collectors.toList());
                StpUtil.getTokenSessionByToken(tokenValue).set("PERMS", ListUtil.list2String(paths));
            }
        }
    }
}

gateway获取角色、权限

方式一:

官方写的实现StpInterfaceImpl中的方法

作为一个异步组件,gateway中不允许使用引起阻塞的同步调用,若使用feign进行调用就会发生错误,我们使用CompletableFuture来将同步调用转换成异步操作,但使用CompletableFuture我们需要指定线程池,否则将会使用默认的ForkJoinPool

这里我们创建一个线程池,用于权限获取使用

代码语言:javascript
复制
package com.weishuang.gateway.gateway.config;

import org.springframework.context.annotation.Configuration;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Configuration
public class ThreadPollConfig {

    private final BlockingQueue<Runnable> asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000);

    public final ExecutorService USER_ROLE_PERM_THREAD_POOL = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors(),
            1000 * 60,
            TimeUnit.MILLISECONDS,
            this.asyncSenderThreadPoolQueue,
            new ThreadFactory() {
                private final AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "RolePermExecutor_" + this.threadIndex.incrementAndGet());
                }
            });

}
StpInterfaceImpl
代码语言:javascript
复制
@Component
public class StpInterfaceImpl implements StpInterface {

    @Autowired
    private RoleFacade roleFacade;

    @Autowired
    private PermissionFacade permissionFacade;

    @Autowired
    private ThreadPollConfig threadPollConfig;

    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        Object res = StpUtil.getTokenSession().get("PERMS");
        if (res == null) {
            CompletableFuture<List<String>> permFuture = CompletableFuture.supplyAsync(() -> {
                // 返回此 loginId 拥有的权限列表
                List<PermissionDTO> permissions = permissionFacade.getPermissions((String) loginId);

                return permissions.stream().map(PermissionDTO::getPath).collect(Collectors.toList());
            }, threadPollConfig.USER_ROLE_PERM_THREAD_POOL);
            try {
                return permFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
        String paths = (String) res;
        System.out.println(paths);
        return ListUtil.string2List(paths);
    }

    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        Object res = StpUtil.getTokenSession().get("ROLES");
        if (res == null) {
            CompletableFuture<List<String>> roleFuture = CompletableFuture.supplyAsync(() -> {
                // 返回此 loginId 拥有的权限列表
                List<RoleDTO> roles = roleFacade.getRoles((String) loginId);

                return roles.stream().map(RoleDTO::getRoleName).collect(Collectors.toList());
            }, threadPollConfig.USER_ROLE_PERM_THREAD_POOL);
            try {
                return roleFuture.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
        String roleNames = (String) res;
        System.out.println(roleNames);
        return ListUtil.string2List(roleNames);
    }

}
gateway配置过滤器,实现鉴权
代码语言:javascript
复制
@Component
public class ForwardAuthFilter implements WebFilter {
    static Set<String> whitePaths = new HashSet<>();


    static {
        whitePaths.add("/account/user/doLogin");
        whitePaths.add("/account/user/logout");
        whitePaths.add("/account/user/register");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {

        ServerHttpRequest serverHttpRequest = serverWebExchange.getRequest();


        String path = serverHttpRequest.getPath().toString();

        //需要校验权限
        if(!whitePaths.contains(path)){

            //判断用户是否有该权限
            if(!StpUtil.hasPermission(path)){
                throw new NotPermissionException(path);
            }
        }

        return webFilterChain.filter(serverWebExchange);
    }
}
方式二:

如果您觉得一定要使用响应式才行,那么无需实现StpInterfaceImpl

代码语言:javascript
复制
/**
 * 全局过滤器
 */
@Component
public class ForwardAuthFilter implements WebFilter {

    @Autowired
    private WebClient.Builder webClientBuilder;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String path = serverHttpRequest.getPath().toString();

        // /api开头的都要鉴权
        if (StringUtils.isNotEmpty(path) && path.startsWith("/api")) {
            Mono<List<String>> permissionList = getPermissionList();

            return permissionList.flatMap(list -> {
                if (!StpUtil.stpLogic.hasElement(list, path)) {
                    return Mono.error(new NotPermissionException(path));
                }
                return chain.filter(exchange);
            });
        }
        return chain.filter(exchange);
    }

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }

    private Mono<List<String>> getPermissionList() {
        String userId = (String) StpUtil.getLoginId();

        Mono<List<PermissionDTO>> listMono = webClientBuilder.build()
                .post()
                .uri("http://weishuang-account/account/permission/getPermissions")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromFormData("userId", userId))
                .retrieve()
                .bodyToFlux(PermissionDTO.class)
                .collectList();

        return listMono.map(permissions -> permissions.stream().map(PermissionDTO::getPath).collect(Collectors.toList()));
    }

}

修改sa-token的配置

代码语言:javascript
复制
@Configuration
public class SaTokenConfigure {

    // 注册 Sa-Token全局过滤器
    @Bean
    public SaReactorFilter getSaReactorFilter() {
        return new SaReactorFilter()
                // 拦截地址
                .addInclude("/**")    /* 拦截全部path */
                // 开放地址
                .addExclude("/favicon.ico")
                // 鉴权方法:每次访问进入
                .setAuth(obj -> {
                    // 登录校验 -- 拦截所有路由,排除白名单
                    SaRouter.match("/**")
                            .notMatch(new ArrayList<>(WhitePath.whitePaths))
                            .check(r -> StpUtil.checkLogin());
                })
                // 异常处理方法:每次setAuth函数出现异常时进入
                .setError(e -> {
                    return SaResult.error(e.getMessage());
                });
    }
}
白名单
代码语言:javascript
复制
public class WhitePath {

    static Set<String> whitePaths = new HashSet<>();

    static {
        whitePaths.add("/account/user/doLogin");
        whitePaths.add("/account/user/logout");
        whitePaths.add("/account/user/register");
    }
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码猿技术专栏 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 需求分析
  • 结构
  • 认证
    • sa-token模块
      • 配置信息
      • 我们来编写一个登录接口
    • gateway模块
      • 依赖
      • 配置
      • 拦截认证
  • 鉴权
    • 配置nacos
      • 配置gateway
        • 实现获取角色、权限接口
          • gateway获取角色、权限
            • 方式一:
            • 方式二:
          • 修改sa-token的配置
            • 白名单
        相关产品与服务
        云数据库 Redis
        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档