Session认证
用户认证的流程:
用户向服务器发送用户名和密码。
服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等。
服务器向用户返回一个session_id,写入用户的Cookie。
用户随后的每一次请求,都会通过Cookie,将session_id传回服务器。
服务器收到session_id,找到前期保存的数据,由此得知用户的身份。
session认证的方式应用非常普遍,但也存在一些问题:扩展性不好。
如果是服务器集群,或者是跨域的服务导向架构,就要求session数据共享,每台服务器都能够读取session,否则当用户分到新的服务器时会要求重新登录(即使之前用户已经登录过了)。
针对此种问题一般有两种方案:
一种解决方案是session数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。
一种方案是服务器不再保存session数据,所有数据都保存在客户端,每次请求都发回服务器。Token认证就是这种方案的一个代表。【但是用户有可能会恶意篡改信息】
Token认证
Token是在服务端产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证。
流程如下:
客户端使用用户名跟密码请求登录,服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个token并把这个token发送给客户端
客户端收到token以后,会把它存储起来,比如放在cookie里或者localStorage里
客户端每次向服务端请求资源的时候需要带着服务端签发的token
服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求的数据。
Token认证的特点
基于token的用户认证是一种服务端无状态的认证方式,服务端不用存放token数据。
用解析token的计算时间换取session的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
token完全由应用管理,所以它可以避开同源策略
JWT
原理:
JSON Web Token(简称JWT)是一个token的具体实现方式,是目前最流行的跨域认证解决方案。
JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,具体如下:
{
"姓名":"张三",
"角色":"管理员",
"到期时间":"2024年9月9日0点0分"
}
用户与服务端通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份。
为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
组成:
JWT的由三个部分组成:
Header(头部)
Payload(负载), 携带的数据
Signature(签名),对前两部分进行签名防止数据被篡改
三部分最终组合为完整的字符串,中间使用.分隔,如下
//Header.Payload.SignatureeyJhbGcioiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIioiIxMjMONTY30DkwIiwibmFtZSI6IkpvaG4gRG91IiwiaXNTb2NpYWwiOnRydWV9.
4pcPyMD09o1PSyXnrXCjTwXyr4BsezdI1AVTmud2fU4
Header
Header部分是一个JSON对象,描述JWT的元数据
{
"a1g":"Hs256",
"typ":"JWT"
}
alg属性表示签名的算法(algorithm),默认是HMAC SHA256(写成HS256),可以自行更改
typ属性表示这个令牌(token)的类型(type),JWT令牌统一写为JWT
最后,将上面的JSON对象使用Base64URL算法转成字符串,方便传输。
Payload
Payload部分也是一个JSON对象,用来存放实际需要传递的数据。JWT规定了7个官方字段,供选用。
iss(issuer):签发人exp(expiration time):过期时i间sub(subject):主题aud(audience):受众nbf(Not Before):生效时i间iat(Issued At):签发时间jti (WT ID):编号
注意,JWT默认是不加密的(签名≠加密),任何人都可以读到,所以不要把秘密信息放在个部分。
这个JSON对象也要使用Base64URL算法转成字符串。
Signature
Signature部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户
然后,使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header)+"."+base64Ur1Encode(payload),secret)
算出签名后,把Header、Payload、Signature三个部分拼成一个字符串,每个部分之间用"点”(’.‘)分隔,就可以返回给用户。
JWT特点
客户端收到服务器返回的JWT,可以储存在Cookie里面,也可以储存在localStorage。
客户端每次与服务器通信,都要带上这个JWT,可以把它放在Cookie里面自动发送,但是这样不能跨域。
更好的做法是放在HTTP请求的头信息’Authorization‘字段里面,单独发送。
JWT的实现
加入依赖:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
生成Token:
//7天过期
private static Long expire 604800;
//32位秘钥
private static String secret "abcdfghiabcdfghiabcdfghiabcdfghi";
//生成token,自定义方法即可
public static String generateToken(String username){Date now new Date();Date expiration new Date(now.getTime()+1000 expire);return Jwts.builder().setHeaderParam("type","JWT") //Header是固定的.setSubject(username) //设置载荷.setIssuedAt(now) //设置生效时间.setExpiration(expiration) //设置过期时间.signwith(SignatureAlgorithm.HS512,secret) //指定签名算法HS512,加入密钥.compact(); //合成,最后我们看到的字符串形式
}
解析Token
//解析token
public static Claims getclaimsByToken(String token){return Jwts.parser().setsigningKey(secret).parseclaimsJws(token).getBody();
}
JWT的实现演示:
创建工具类,用于创建(生成)jwt字符串和解析jwt。
package com.example.jwt_demo.jwtdemo.Util;import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.Date;
import java.util.Map;@Component //将该类标记为一个Spring的组件,这样Spring框架会自动管理它的生命周期,并且可以在其他地方通过依赖注入使用它。
public class JWTUtil {@Value("${jwt.secretKey}")private String secretKey;//生成JWTpublic String createJWT(String id, String subject, long ttlMillis, Map<String, Object> map) throws Exception {JwtBuilder builder = Jwts.builder().setId(id).setSubject(subject) //发行者.setIssuedAt(new Date()) //发行时间.signWith(SignatureAlgorithm.HS256, secretKey) //签名类型、钥.compressWith(CompressionCodecs.DEFLATE); //对载荷进行压缩'//处理自定义的Claims/*如果 map 不为空,调用 setClaims(map) 来将自定义的 claims(负载)添加到 JWT 中。Claims 是 JWT 的主体信息,包含了关于用户或上下文的相关信息,可以自定义存储用户角色、权限、用户 ID 等。*/if (!CollectionUtils.isEmpty(map)) {builder.setClaims(map);}//设置有效期/*如果 ttlMillis 大于 0,使用 setExpiration 方法设置 JWT 的过期时间。过期时间表示 JWT 的有效期,超过这个时间,JWT 就会失效。这里是通过 当前时间+有效期ttlMillis 来计算出过期时间。*/if (ttlMillis > 0) {builder.setExpiration(new Date(System.currentTimeMillis() + ttlMillis));}//生成 JWT 字符串/*调用 compact() 方法生成最终的 JWT 字符串。这是经过签名和编码的字符串,可以在客户端和服务端之间传输。*/return builder.compact();}//解析 JWT (parseJWT 方法)/*用于解析客户端传来的 JWT 字符串,并返回其中的 Claims(负载)信息。*/public Claims parseJWT(String jwtString) {return Jwts.parser().setSigningKey(secretKey) //创建一个 JWT 解析器,并设置签名的密钥(必须与生成时的 secretKey 一致).parseClaimsJws(jwtString) //对传入的 JWT 字符串进行解析,并验证其合法性(例如,签名是否匹配、是否过期等).getBody(); //返回 JWT 的主体 Claims,其中包含了之前设置的 id、subject 和自定义的 map 信息}}
接着在application.yml配置文件配置jwt.secretKey。