基于Session的认证方式
在分布式的环境下,基于session的认证会出现一个问题,每个应用服务都需要在session中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将session信息带过去,否则会重新认证。我们可以使用Session共享、Session黏贴等方案。
Session方案也有缺点,比如基于cookie,移动端不能有效使用等
基于Token的认证方式
优点:服务端不用存储认证数据;易维护扩展性强;客户端可以把token 存在任意地方;可以实现web和app统一认证机制
缺点:token由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
作用:在头部指明了签名算法
例如:{“typ”:“JWT”,“alg”:“HS256”}
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分
作用:载荷就是存放有效信息的地方
例如:{“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}
将其进行base64加密,得到Jwt的第二部分
作用:jwt的第三部分是一个签证信息,这个签证信息由三部分组成
例如:HMACSHA256( base64UrlEncode(header) + “.” + base64UrlEncode(payload)+ secret )
JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
1)在项目中的pom.xml中添加依赖
<dependencies>
<!--JJWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
2)创建测试类,代码如下
JwtBuilder builder= Jwts.builder()
.setId("55555") //设置唯一编号
.setSubject("大饼")//设置主题 可以是JSON数据
.setIssuedAt(new Date())//设置签发日期
.signWith(SignatureAlgorithm.HS256,"dabing");//设置签名 使用HS256算法,并设置SecretKey(字符串)
//构建 并返回一个字符串
System.out.println( builder.compact() );
运行打印结果:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5NTI3Iiwic3ViIjoi5YWD5pWsIiwiaWF0IjoxNjEzOTgwMjIyfQ.Uj-6H3nOcUWerJb9PEsq32OSQCPhKtqmE2bJtnV-E1Q
再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。
创建token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。
String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5NTI3Iiwic3ViIjoi5YWD5pWsIiwiaWF0IjoxNjEzOTgwMjIyfQ.Uj-6H3nOcUWerJb9PEsq32OSQCPhKtqmE2bJtnV-E1Q";
Claims claims = Jwts.parser().setSigningKey("dabing").parseClaimsJws(compactJwt).getBody();
System.out.println(claims);
运行打印效果:
{jti=55555, sub=大饼, iat=1613980222}
.setExpiration(date) //用于设置过期时间 ,参数为Date类型数据
有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。
上面只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。
创建测试类,并设置测试方法:
.claim(“roles”,“admin”) //设置角色
用户登录之后要签发Token,用户访问任何非登录的资源时都需要携带Token,并且在网关进行Token验证。
1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
4.2.1 依赖:
<!--鉴权-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
4.2.2 创建JWTUtil类
/**
* jwt校验工具类
*/
public class JwtUtil {
//有效期为
public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时
//设置秘钥明文
public static final String JWT_KEY = "dabing";
/**
* 生成加密后的秘钥 secretKey
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
4.2.3 在网关微服务中编写过滤器
/**
* 鉴权过滤器 验证token
*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
private static final String AUTHORIZE_TOKEN = "token";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1. 获取请求
ServerHttpRequest request = exchange.getRequest();
//2. 则获取响应
ServerHttpResponse response = exchange.getResponse();
//3. 如果是登录请求则放行
if (request.getURI().getPath().contains("/admin/login")) {
return chain.filter(exchange);
}
//4. 获取请求头
HttpHeaders headers = request.getHeaders();
//5. 请求头中获取令牌
String token = headers.getFirst(AUTHORIZE_TOKEN);
//6. 判断请求头中是否有令牌
if (StringUtils.isEmpty(token)) {
//7. 响应中放入返回的状态吗, 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//8. 返回
return response.setComplete();
}
//9. 如果请求头中有令牌则解析令牌
try {
JwtUtil.parseJWT(token);
} catch (Exception e) {
e.printStackTrace();
//10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//11. 返回
return response.setComplete();
}
//12. 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}