对于微服架构的项目,不同的微服务会有不同的网络地址,
外部客户端可能需要调用多个服务的接口才能完成一个业务需求,
如果让客户端直接与各个微服务通信,会有以下的问题:
增加了客户端的复杂性
以上这些问题可以借助网关解决。
假设一个项目存在很多的角色: 不同的角色有不同的权限功能就可以通过网关来进行管理:
总结:
(网络应用环境间传递声明而执行的一种基于JSON的开放标准)
**说起JWT,我们应该来谈一谈
token的鉴权机制
传统的session认证
的区别服务器存储(Session)
一份用户登录的信息
这份信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用; 这样我们的应用就能识别请求来自哪个用户了Session 问题:
而且:
微服务架构...
一个功能就是一个模块,一个模块就是一个独立的服务器.token的鉴权机制 类似于http协议也是无状态的
它不需要在服务端去保留用户的认证信息或者会话信息。
这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
流程上是这样的:
• 用户使用用户名密码来请求服务器
• 服务器通过验证发送给用户一个token
• 客户端存储token,并在每次请求时附送上这个token值
• 服务端验证token值,并返回数据
你第一次登录成功,服务器给你生成一个令牌/身份证(Token),
下次在来请求带着令牌来如果没有或错误,不允许登录!并根据令牌得知你是那个用户!
因为 Jwt 可以搭配 Secret密钥, 来进行解密, 不同微服模块,就可以通过解码
来获得对应的用户信息!(存储信息!)
一个JWT实际上就是一个字符串**逗号分隔
**,它由三部分组成: 头部
载荷
签证
**。**
头部用于描述关于该JWT的最基本的信息
一般默认为:一般不需要用户自定义,程序默认
{"typ":"JWT","alg":"HS256"} //头部指明了签名算法是HS256算法;
之后会进行BASE64编码http://base64.xpcha.com/,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 #这就是通常JWT的头部.存放着签名的算法信息!
载荷就是存放有效信息的地方。 (用户名/密码/…. 重要信息的地方,进行加密!)
一般由开发者编写!
一般包括以下部分:**或存储一些重要的信息:用户信息..
**
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
比如下面这个 Payload
使用了 sub 还有两个开发者自定义的字段!这里就像一个存储重要信息的仓库.
{"sub":"1234567890","name":"John Doe","admin":true}
//sub 通常表示用户id
//name 用户名
//admin 是否管理员
base64加密,得到Jwt的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
jwt的第三部分是一个签证信息,这个签证信息**由三部分组成
**
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
(header+payload)
,再用加密算法加密一下,加密的时候要放进去一个 Secret
// (header+payload)
加密信息合并
var encodedString = base64UrlEncode(header) + “.” + base64UrlEncode(payload);
// HMACSHA256加密算法加密(haeder中指定的加密算法)
, 并加密过程附带一个 Secret密钥
HMACSHA256(encodedString, ‘secret’);
处理完成以后看起来像这样:
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
这就是 signature;此时已经有了三段 密文了
**, 最后在将三段密文合并在一起, 三者之间通过** .
点来间隔区分…就形成了 JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
① 在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
② 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
③ 使用在header中声明的加密算法和每个项目随机生成的secret来进行加密,
把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
④ 解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密。
上面的模块其实,无伤大雅…最关键的就是:**pom依赖
** 和 TestJWT.Java
pom.xml
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
TestJWT.Java
package com.wsm.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
//JWT 测试类
public class TestJWT {
public static void main(String[] args) {
//打印: 创建的JWT
String jwt = createToken();
System.out.println(jwt);
System.out.println("---------------------------------------------------------------------------------------");
//打印: JWT的解析数据
System.out.println(getToken(jwt));
}
//创建 JWT
public static String createToken() {
JwtBuilder jwt = Jwts.builder()
.setId("110") //设置唯一编号
.setSubject("{'id':1,'name':'张三'}") //设置主题,可以是JSON数据(一般是要存储的用户信息!)
.setIssuedAt(new Date()) //设置签发日期
.signWith(SignatureAlgorithm.HS256, "xzzb"); //设置签名 使用HS256算法,并设置SecretKey(字符串)
String compact = jwt.compact(); //生成 JWT;
return compact;
}
//解析 JWT
public static String getToken(String token) {
//调用 Jwts.parser(); 方法完成解析;
Claims xzzb = Jwts.parser()
.setSigningKey("xzzb") //明确Secret 解析私钥;
.parseClaimsJws(token) //要解析的Token;
.getBody(); //JSON 格式;
return xzzb.toString(); //返回!
}
}
运行 main
类中存在两个方法: createToken()
getToken()
创建/解决JWT
因为生成Jwt 里面加了时间
Secret密钥
和 JWT Token
又一次获得数据…我们刚才的例子只是存储了id和subject两个信息,
如果你想存储更多的信息(例如角色)可以定义自定义claims。
通过:
JwtBuilder jwtBuilder = Jwts.builder();
jwtBuilder.addClaims(Map);
//可以存储自定义Map 类型数据存储!编码!
基于上面。**JWT Demo
** 我们可以实现:自定义jwt 的 Token, 并对其进行 解码
有些操作不用登录也可以访问, 京东的查看商品... 这种请求一般直接放行!
如果需要,识别用户的身份是否能访问该路径这里可以基于数据库设计一套权限
如果需要权限访问,用户已经登录,则放行。
如果需要权限访问,且用户未登录,则提示用户需要登录。为了方便操作,这里提供了一个便于快速生成 JWT的工具类:**JwtUtil.Java
**
一般定义在公共的 api模块中, 注意需要引入 pom.xml依赖哦!
服务模块引用 api 模块!
JwtUtil.Java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时
//Jwt令牌信息
public static final String JWT_KEY = "xzzb";
/**
* 生成加密 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
*
* @param id jwt 唯一id
* @param subject 设置主题,可以是JSON数据(一般是要存储的用户信息!)
* @param ttlMillis 设置过期时间....毫秒;
* @return
*/
public static String createJWT(String id, String subject, Long ttlMillis) {
//指定算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//当前系统时间
long nowMillis = System.currentTimeMillis();
//令牌签发时间
Date now = new Date(nowMillis);
//如果令牌有效期为null,则默认设置有效期1小时
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
//令牌过期时间设置
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
//生成秘钥
SecretKey secretKey = generalKey();
//封装Jwt令牌信息
JwtBuilder builder = Jwts.builder()
.setId(id) //唯一的ID
.setSubject(subject) // 主题 可以是JSON数据
.setIssuer("admin") // 签发者
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey) // 签名算法以及密匙
.setExpiration(expDate); // 设置过期时间
return builder.compact();
}
/**
* 解析令牌数据
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
其中包含三个方法: 生成密钥Secret
生成jwt
解析jwt
这里就是主要完成登录的操作了! 这次没有用户类就直接使用 map了...
的小案例了;
如果是接着上面项目做的建议,把Token限流给关了!
用户serice UserService.Java
/**
* @param name 用户名
* @param pwd 密码
* @return
*/
public HashMap<String, Object> userLogin(String name, String pwd) {
//登录
if (name.equals("admin") && pwd.equals("123")) {
HashMap<String, Object> user = new HashMap<>();
user.put("id", 1);
user.put("username", name);
user.put("passwd", pwd);
return user;
}
return null;
}
controller UserController.Java
//执行登录方法!
@PostMapping("/login")
public HashMap<String, Object> login(String name, String pwd) {
HashMap<String, Object> user = userService.userLogin(name, pwd);
if (user != null) {
String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(user), null);
HashMap<String, Object> result = new HashMap<>();
result.put("code", "1001");
result.put("token", jwt);
return result;
}
return null;
}
修改 TokenFitter.Java
import com.wsm.jwt.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
//Gate way实现过滤器:
@Component
@Slf4j
public class TokenFitter implements GlobalFilter, Ordered { //自定义过滤器类 实现GlobalFilter Ordered接口并实现两个方法;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取request response
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//获取请求url 打印!
String path = request.getURI().getPath();
System.out.println(path);
//判断请求... 对于请求 /user/login /api/address/get 这请求网关可以直接放行...
//对于一些操作本身就是不登录也可也访问... 登录请求当然直接放行,本身就是登录才有Token 不登录那里来Token 放行!
if(path.startsWith("/user/login") || path.startsWith("/api/address/get")){
System.out.println("Token登录 网关同行!!");
return chain.filter(exchange);
}
//获取请求中的Token 从请求头中获取Token
String token = request.getHeaders().getFirst("token");
//不存在 则从请求中查找, 有没有Token
if(StringUtils.isEmpty(token)){
token=request.getQueryParams().getFirst("token");
}
//还不存在则,拦截器拦截!
if(StringUtils.isEmpty(token)){
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
try {
//验证Token 是否存在/合法...
Claims claims = JwtUtil.parseJWT(token); //不合法出现异常,也直接请求拦截!!
} catch (Exception e) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
//以上都成立, 放行!!
return chain.filter(exchange);
}
//过滤顺序级别
@Override
public int getOrder() {
return 0;
}
}
结合,网关的过滤器进行 微服网关! Token的操作执行…
为了确保http 数据传递安全, 对数据进行加密!传递的一种 编码/解码技术
BASE64 存在解码所有并不安全!
所以后面又存在一个 加盐的操作! 二次加密/多次加密!(连续多次加密!)
欧克, 终于写完了。。。 上面一系列的操作都来源一个项目:SpringCloudBJ