参考视频

Session认证

提要

互联网用户认证的一般流程:

  • 用户向服务器发送用户名和密码
  • 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登陆时间等。
  • 服务器向用户返回一个session_id,写入用户的Cookie
  • 用户随后的每一次请求,都会通过Cookie,将session_id传回服务器
  • 服务器收到session_id,找到前期保存的数据,由此得知用户的身份

Session的问题与解决方案

session认证扩展性不好,如果是服务器集群,或者是跨域的武器导向架构,就要求session数据共享,每台服务器都能够读取session,针对这种问题一般有两种方案:

  • session数据持久化,写入数据库或者别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大
  • 服务器不再保存session数据,所有数据都保存在客户端,每次请求都发回服务器。Token认证就是这种方案的代表

Token认证

是服务器产生的一串字符串,是客户端访问资源接口(API)时所需要的资源凭证,流程如下:

  • 客户端使用用户名跟密码请求登录,服务器收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个token并把这个token发送给客户端
  • 客户端收到token以后,会把它存储起来,比如放在cookie里或者localStorage
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的token
  • 服务器收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求的数据

特点

  • 基于token的用户认证是一种服务端无状态的认证方式,服务端不用存放token数据
  • 用解析token的计算时间换取session的存储时间,从而减轻服务器的压力,减少频繁的查询数据库
  • token完全由应用管理,所以它可以避开同源策略

JWT的使用

概述

  • JWT(JSON Web Token)是一个token的具体实现方式,是目前最流行的跨域认证解决方案
  • JWT的原理是,服务器认证以后,生成一个JSON对象,发回给用户,具体如下:
  • 用户与服务器通信的时候,都要发回这个JSON对象。服务器完全只靠这个对象认定用户身份
  • 为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名

组成

JWT由三部分组成:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)
    三个部分最终组合为完整的字符串,中间使用.分割:
  • Header.Payload.Signature
  • Header部分是一个JSON对象,描述JWT的元数据
1
2
3
4
{
"alg": "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):过期时间
    • sub(subject):主题
    • aud(audience):受众
    • nbf(Not before):生效时间
    • iat(Issued At):签发时间
    • jti(JWT ID):编号
  • 注意,JWT默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
  • 这个JSON对象也要使用Base64URL算法转成字符串

Signature

  • Signature 部分是对前两部分的签名,防止数据篡改
  • 首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄漏给用户
  • 然后,使用Header里面指定的签名算法(默认是HMAC SHA256),按照下面的公式产生签名
1
2
3
4
HMACSHA256(
base64UrlEncode(header)+"."+
base64UrlEncode(payload),
secret)

特点

  • 客户端收到服务器返回的JWT以后,可以放在cookie里或者localStorage
  • 客户端每次与服务器通信,都要带上这个JWT,可以把它放在Cookie里面自动发送,但是这样不能跨域
  • 更好的做法是放在HTTP请求的头信息Authorization字段里面,单独发送

JWT的实现(java)

  • 加入依赖
1
2
3
4
5
6
<!--引入jwt-->
<dependency>
<groupId>com.xiajibaxie</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

生成Token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//七天过期
private static long expire = 604800
//32位密钥
private static String secret = "uhew25253hui5h29p35h198h81j31ksi"
//生成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")
.setSubject(username)
.setIssueAt(now)
.setExpiration(expiration)
.signWith(SignatureAlgorithm.HS512,secret)
.compact();
}

解析Token

1
2
3
4
5
6
7
//解析Token
public static Claims getClaimsByToken(String token){
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}