微服务聚合JWT实现权鉴
Posted 循环网络不循环
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了微服务聚合JWT实现权鉴相关的知识,希望对你有一定的参考价值。
整体流程如下:
第一步:使用java自带工具生成证书
在jdk的bin目录下用cmd命令操作keytool工具生成证书,记得以管理员身份运行否则会失败,命令如下:
keytool -genkeypair -alias jwt -keyalg RSA -keypass 123456 -keystore jwt.jks -storepass 123456
-
Keytool 是一个java提供的证书管理工具
- alias:密钥的别名
- keyalg:使用的hash算法
- keypass:密钥的访问密码
- keystore:密钥库文件名,secre.jks保存了生成的证书
- storepass:密钥库的访问密码
得到文件后将文件拷贝到鉴权微服务的source目录下。
第二步:添加依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
第三步:添加权鉴方法
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JwtTemplate
// 证书文件
private String path = "jwt.jks";
// 密钥库文件的密码
private String keyStoreSecurity = "123456";
// 密钥库的别名
private String alias = "jwt";
/**
* 创建密钥对对象
*/
private KeyPair keyPair()
// 创建密钥对对象
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
new ClassPathResource(path),keyStoreSecurity.toCharArray()
);
KeyPair keyPair = factory.getKeyPair(alias);
return keyPair;
/**
* 创建签名器
*/
private JWTSigner jwtSigner()
return JWTSignerUtil.createSigner("RSA",keyPair());
/**
* 【生成token】
*/
public String createToken(Map<String,Object> playload)
return JWTUtil.createToken(playload,jwtSigner());
/**
* 【校验token】
*/
public boolean verify(String token)
return JWTUtil.verify(token, jwtSigner());
/**
* 【解析token】
*/
public Object parseToken(String token,String key)
JWT jwt = JWTUtil.parseToken(token);
return jwt.getPayload(key);
第四步:添加依赖
这里添加权鉴微服务的依赖以便调用
<dependency>
<groupId>com.woniu</groupId>
<artifactId>sk-common-jwt</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
第五步:网关yml文件添加配置
这里的path就是权鉴证书名,别名和秘钥要和前面保持一致。
auth:
security:
path: jwt.jks
keyStoreSecurity: 123456
alias: jwt
第六步:增加redis和JWT的配置类
Redis配置类
@Configuration
public class RedisConfig
/**
* 配置RedisTemplate的序列化方式
*/
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory)
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(factory);
// 指定key的序列化方式:string
redisTemplate.setKeySerializer(RedisSerializer.string());
// 指定value的序列化方式:json
redisTemplate.setValueSerializer(RedisSerializer.json());
return redisTemplate;
JWT的配置类
@Configuration
public class JwtConfig
@Value("$auth.security.path")
private String path;
@Value("$auth.security.keyStoreSecurity")
private String keyStoreSecurity;
@Value("$auth.security.alias")
private String alias;
@Bean
public JwtTemplate jwtTemplate()
return new JwtTemplate(path,keyStoreSecurity,alias);
第七步:增加过滤器
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered
@Autowired
private JwtTemplate jwtTemplate;
@Autowired
private RedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 1、获取请求路径,并进行判断路径中是否包含/auth
String path = request.getURI().getPath();
AntPathMatcher pathMatcher = new AntPathMatcher();
// 请求路径如:/promotion-seckill/auth/order/100
boolean match = pathMatcher.match("/**/auth/**", path);
if (match) // 需要对当前路径进行鉴权
//2. 鉴权要求:请求头中Authorizaiton中携带token
String token = request.getHeaders().getFirst("Authorization");
if (StringUtils.isEmpty(token))
// 鉴权失败,返回错误
log.error("请求头中未携带token!");
return error(response);
//3. 请求头中的token值的格式: Bearer xx
token = token.replace("Bearer ","");
//4. token校验, 校验失败:JWTException: The token was expected 3 parts, but got 1.
boolean verify = jwtTemplate.verify(token);
if (!verify)
log.error("token不合法!");
return error(response);
//5. 解析token,获取token中存储的userId。后面会作为redis中的key
String userId = (String) jwtTemplate.parseToken(token, "userId");
//6. 从Redis中获取token并校验
String tokenKey = "user:token:"+userId;
String redisToken = (String) redisTemplate.opsForValue().get(tokenKey);
if (StringUtils.isEmpty(redisToken))
log.error("token无效!");
return error(response);
//7. 重写请求头,把userId放入请求头中;这样各个微服务就可以从请求头中获取认证的userId
ServerHttpRequest.Builder mutate = request.mutate();
mutate.header("userId",userId);
//8. 更新redis中token的有效时间
redisTemplate.expire(tokenKey, Duration.ofMinutes(30));
// 放行,执行下一个过滤器或者执行微服务
return chain.filter(exchange);
// 自动处理检查异常的注解
@SneakyThrows
private Mono<Void> error(ServerHttpResponse response)
Result<Object> result = Result.fail(ResultCode.PERMISSION_NO_ACCESS);
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = objectMapper.writeValueAsBytes(result);
DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(dataBuffer));
// 控制过滤器执行顺序,数字越小越先执行
@Override
public int getOrder()
return 0;
通过多个微服务进行 JWT 授权
【中文标题】通过多个微服务进行 JWT 授权【英文标题】:JWT Authorisation through multiple microservices 【发布时间】:2021-09-04 17:08:14 【问题描述】:简短的问题
为用户处理 JWT 身份验证的正确方法是什么 谁正在访问来自微服务 A 的数据,但微服务 A 需要来自微服务 B 的数据。
设置
我正在使用 Auth0 来发布和处理身份验证和授权。
我设置了两个微服务。用户在前端登录,需要加载基于租户的计费信息。
微服务 A 是一种聚合服务,它与多个不同的服务进行通信,以便为不同类型的数据类型提供标准化的响应。
微服务 A 查询微服务 B,以检索用户拥有的车辆信息。
哪种解决方案是正确的方法?
解决方案 A: 用户登录时颁发给用户的令牌将被 MS A 用于与 MS B 通信。MS A 本质上是转发用户提供的令牌。
解决方案 B: MS A 有自己的 JWT,它具有超级管理员权限,可以访问来自 MS B 的任何资源。当从 MS B 访问资源时,MS A 将使用此令牌,并且 MS A 将负责确保不会返回用户无法访问的资源聚合数据集。
【问题讨论】:
【参考方案1】:在解决方案 B 中,您让 MS A 负责正确处理数据。在我看来,这只是自找麻烦,最终有人会设法以这种方式致电 MS A,以获得比他们可以访问的更多的数据。所以我会选择解决方案 A,或者它的一个变体。有几种方法可以在服务之间共享令牌 - 传递相同的令牌、嵌入令牌或交换令牌。前段时间写了一篇关于Token Sharing的文章,大家可以看看。
【讨论】:
【参考方案2】:我认为这取决于 - 两个选项都可以,具体取决于场景。
这里有几点需要考虑。
微服务 B 是否进行任何资源所有者授权,例如使用来自用户 JWT 的sub
声明?如果是这样,通过用户访问令牌可能会更容易。 注意:通常我只使用从 JWT 推断出的数据用于 authZ 逻辑。如果您需要资源所有者 ID 之类的内容作为业务逻辑的一部分,则应将其明确包含在请求负载中,以便后端服务也可以使用 M2M 令牌使用该服务。
使用 Auth0,机器 MUA(每月唯一身份验证)需要付费。这与用户 MUA 的成本是分开的。对于企业帐户,默认值为 1000 个 M2M MUA。增加这个数字非常便宜,但仍然需要考虑。通常,您应该将 M2M 令牌配置为具有更长的有效期(例如 1-2 天)并使用令牌缓存。通常,内存缓存就足够了,但如果您正在构建无服务器应用程序(例如 AWS Lambda、Azure 函数应用程序等),您可能需要一个分布式缓存。所有这些都是 M2M 令牌的额外工作、复杂性和潜在成本(例如分布式缓存)。
M2M 身份验证有额外的网络请求(到 Auth0)。这可以通过使用令牌缓存来缓解,但也需要考虑一些因素。
微服务 B 是否可以公开访问?如果是,您可能需要考虑一些安全问题,因为用户将能够使用他们的访问令牌(合法获得)直接调用微服务 B,但他们可能会以您不希望的方式篡改请求。
我工作的公司混合使用了您提到的两种方法(通常取决于具体情况)。我们有一些用于令牌缓存等的辅助工具,这使这两种方法都足够简单,但如果您要使用 M2M 令牌,您可能需要提前做一些额外的工作。
最后一件事要提;在实现像微服务 B 这样的“传递”服务时,我认为最好确保用户和机器令牌都可以使用该服务。您的 authZ 政策可能允许其中一种(或两种),但至少您可以在需要时灵活更改。
【讨论】:
以上是关于微服务聚合JWT实现权鉴的主要内容,如果未能解决你的问题,请参考以下文章
微服务架构 | 10.2 使用 Papertrail 实现日志聚合