有状态登录(session认证)
服务器当中记录每一次的登录信息,从而根据客户端发送的数据来判断登录过来的用户是否合法。
缺点:
每个用户登录信息都会保存到服务器的session中,随着用户的增多服务器的开销会明显增大;
由于session存储在服务器的物理内存当中,所以在分布式系统当中这种方式将会失效。
当然我们也可以通过分布式session来解决相关问题,比如将session信息存储到Redis中,但这无疑会提升系统的复杂度。
因为session认证本质基于cookie,而移动端及非浏览器应用通常没有cookie,故对非浏览器的客户端、手机移动端等不适用;
由于基于cookie,而cookie无法跨域,所以session的认证也无法跨域,对单点登录不适用;
无状态登录(token认证)
服务器当中不记录用户的登录信息,而是将登录成功后的合法用户信息以token方式保存到客户端当中,用户在每次请求都携带token信息。
优点:
减轻服务端存储session的压力;
支持分布式,支持单点登录并对移动端友好;
支持跨域;
JWT及JSON Web Token,是一种在两方之间以紧凑、可验证的形式传输信息的方式。此信息可以验证和信任,因为它是数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
什么时候使用JWT
授权:这是使用JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够在不同的域中轻松使用。
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为可以对 JWT 进行签名(例如,使用公钥/私钥对),所以您可以确定发件人就是他们所说的那个人。此外,由于使用标头和有效负载计算签名,您还可以验证内容没有被篡改。
JWT结构
JWT由以( . )分隔的三部分组成,它们是:
标头(Header)
有效荷载(Payload)
签名(Signature)
因此,JWT 通常如下所示:xxxxx.yyyyy.zzzzz。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串:
JWTString = Base64(Header).Base64(Payload).HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 定义了一个标准,JJWT 是 JWT 基于 Java 的一个实现。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
public class JWTTest01 {
public static void main(String[] args) {
// ???????m1();
m2();
}
static void m1() {
// 生成令牌
JwtBuilder jwtBuilder = Jwts.builder()
.setId("888")//唯一ID
.setSubject("zhao")//接受用户
.setIssuedAt(new Date())//签发时间
.signWith(SignatureAlgorithm.HS512, "1234");//签名算法、和秘钥
// secret key byte array cannot be null or empty.
// key不能太短 最短四个字符!!!
String token = jwtBuilder.compact();
System.out.println("生成令牌:【" + token + "】");
}
static void m2(){
// 解析令牌
String token = "eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiJ6aGFvIiwiaWF0IjoxNjk0MzUyMTk2fQ.cFeamoabpwXcPp49DzHhyOL-LAkpMUiz9Tu-ai7efg6xhTQy1TWpmd0mY6BH2aOEWwZ4dF5wNPFnBM5hTXtm7Q";
Claims claims = (Claims) Jwts.parser()
.setSigningKey("1234")
.parse(token)
.getBody();
System.out.println(claims.getId());
System.out.println(claims.getSubject());
System.out.println(claims.getIssuedAt());
System.out.println("解析令牌:【" + claims + "】");
}
}
public class JWTTest02 {
/**
* 使用JWT令牌时需要注意:
*
* JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
* 如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。
*
* @param args
*/
public static void main(String[] args) {
// ???????m1();
m2();
}
// (1)生成JWT代码实现
public static void m1() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("username", "Tom");
String jwt = Jwts.builder()
.setClaims(claims) //自定义内容(载荷)
.signWith(SignatureAlgorithm.HS256, "itheima") //签名算法
.setExpiration(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)) //有效期
.compact();
System.out.println(jwt);
}
public static void m2() {
Claims claims = Jwts.parser()
.setSigningKey("itheima")//指定签名密钥(必须保证和生成令牌时使用相同的签名密钥)
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjk0MzIzODExLCJ1c2VybmFtZSI6IlRvbSJ9.VDNnX-n68QjIIRpmL3Ktcj9K5V0UWcPHlp09qA4odk4")
.getBody();
System.out.println(claims);
}
}
public class JwtUtils {
private static String signKey = "zhaoyang";//签名密钥
private static Long expire = 43200000L; //有效时间
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)//自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}