✅作者:TuNan ✨个人主页:图南的个人主页 欢迎关注点赞收藏⭐留言

本文章使用的版本: SpringBoot:2.6.13 JDK:1.8 SpringCloud Alibaba:2021.0.5.0 Satoken:1.37.0 微服务架构下的鉴权一般分为两种:

每个服务各自鉴权网关统一鉴权 方案一和传统单体鉴权差别不大,不再过多赘述,本篇介绍方案二的整合步骤(以及在下游微服务、服务与服务之间传递用户信息):

1.流程梳理

大致流程与下图相似只是将jwt替换为了satoken

2.项目架构

3.在网关中引入如下依赖和配置 依赖:

cn.dev33

sa-token-reactor-spring-boot-starter

1.37.0

配置:

package team.weyoung.gateway.config;

import cn.dev33.satoken.reactor.filter.SaReactorFilter;

import cn.dev33.satoken.util.SaResult;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

/**

* [Sa-Token 权限认证] 配置类

*

* @author Tunan

*/

@Configuration

public class SaTokenConfigure {

// 注册 Sa-Token全局过滤器

@Bean

public SaReactorFilter getSaReactorFilter() {

return new SaReactorFilter()

// 拦截地址

.addInclude("/**") /* 拦截全部path */

// 开放地址

.addExclude("/favicon.ico")

// 鉴权方法:每次访问进入

.setAuth(obj -> {

// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录

// todo 这里的路由需要根据实际情况修改

//SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());

})

// 异常处理方法:每次setAuth函数出现异常时进入

.setError(e -> {

return SaResult.error(e.getMessage());

})

;

}

}

这里还需要一个过滤器将参数转发到下游微服务

package team.weyoung.gateway.config;

import cn.dev33.satoken.stp.StpUtil;

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;

/**

* 全局过滤器,为请求添加 satoken 参数

*

* @author kong

*

*/

@Component

public class SaIdTokenFilter implements GlobalFilter, Ordered {

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

ServerHttpRequest newRequest = exchange

.getRequest()

.mutate()

// 为请求追加 satoken 参数

.header("satoken",StpUtil.getTokenValue())

.build();

ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();

return chain.filter(newExchange);

}

@Override

public int getOrder() {

return 0;

}

}

4.在common模块引入以下依赖和配置(其他服务依赖于common模块) 依赖:

cn.dev33

sa-token-reactor-spring-boot-starter

1.37.0

配置(注册全局拦截器)

package team.weyoung.ojmodel.satoken;

import cn.dev33.satoken.context.SaHolder;

import cn.dev33.satoken.filter.SaServletFilter;

import cn.dev33.satoken.interceptor.SaInterceptor;

import cn.dev33.satoken.stp.StpUtil;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**

* Sa-Token 代码方式进行配置

*

* @author kong

*/

@Configuration

public class SaTokenConfigure implements WebMvcConfigurer {

/**

* 注册 Sa-Token 的拦截器,打开注解式鉴权功能

*/

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");

}

/**

* 注册 Sa-Token全局过滤器,解决跨域问题

*/

@Bean

public SaServletFilter getSaServletFilter() {

return new SaServletFilter()

// 拦截与排除 path

.addInclude("/**").addExclude("/favicon.ico")

// 全局认证函数

.setAuth(obj -> {

// 校验 Id-Token 身份凭证 —— 以下两句代码可简化为:SaIdUtil.checkCurrentRequestToken();

String token = SaHolder.getRequest().getHeader("satoken");

StpUtil.getTokenSessionByToken(token);

})

.setBeforeAuth(obj -> {

})

;

}

}

5.用户服务 引入依赖:

cn.dev33

sa-token-redis-jackson

1.37.0

org.apache.commons

commons-pool2

yml文件:

############## Sa-Token 配置 (文档: https://sa-token.cc) ##############

sa-token:

# token 名称(同时也是 cookie 名称)

token-name: satoken

# token 有效期(单位:秒) 默认30天,-1 代表永久有效

timeout: 2592000

# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结

active-timeout: -1

# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)

is-concurrent: true

# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)

is-share: true

# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)

token-style: uuid

# 是否输出操作日志

is-log: true

# 是否从cookie中读取token

is-read-cookie: false

最后在我们的用户服务就可以使用StpUtil获取登录信息了,这里我使用的是无cookie模式将tokenvalue塞到返回值里面给到前端,前端可以使用pinia或者localstorage存储token并在每次请求前在请求头加上satoken即可完成鉴权

@Override

public LoginUserVO userLogin(String userAccount, String userPassword) {

// 1. 校验

if (StringUtils.isAnyBlank(userAccount, userPassword)) {

throw new BusinessException(HttpCodeEnum.PARAMS_ERROR, "参数为空");

}

if (userAccount.length() < 4) {

throw new BusinessException(HttpCodeEnum.PARAMS_ERROR, "账号错误");

}

if (userPassword.length() < 8) {

throw new BusinessException(HttpCodeEnum.PARAMS_ERROR, "密码错误");

}

// 2. 加密

String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());

// 查询用户是否存在

QueryWrapper queryWrapper = QueryWrapper.create()

.from(USER)

.where(USER.USER_ACCOUNT.eq(userAccount))

.where(USER.USER_PASSWORD.eq(encryptPassword));

User user = userMapper.selectOneByQuery(queryWrapper);

// 用户不存在

if (user == null) {

log.info("user login failed, userAccount cannot match userPassword");

throw new BusinessException(HttpCodeEnum.PARAMS_ERROR, "用户不存在或密码错误");

}

// 3. 记录用户的登录态

long userId = user.getId();

StpUtil.login(userId);

StpUtil.getSession().set(SaSession.USER, user);

SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

System.out.println(tokenInfo);

LoginUserVO loginUserVO = this.getLoginUserVO(user);

loginUserVO.setToken(tokenInfo);

return loginUserVO;

}

参考文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: