1.Session与Token
Http协议是无状态协议,所谓无状态协议就是指每次请求间都相互独立。比如用户A、B同时向服务器发送Http请求,服务器无法区分到底是用户A还是用户B发送的。因此,需要在客户端与服务器端通过某种手段来区分来自不同用户的请求。 Session是其中的一种方式。Session是一种记录服务器和客户端状态的机制。Session是基于Cookie实现的,Session存储在服务器端,SessionId通过Cookie存储在客户端。
Session的实现原理
- 客户端首次访问服务器时,服务器端会创建Session,并将SessionId返回至客户端
- 客户端通过Cookie记录SessionId以及该SessionId对应的域名
- 客户端再次访问服务器时,浏览器判断是否存在于该域名相对应的Cookie,如果有,则将该Cookie发送给服务端,服务端收到Cookie后,判断其中是否包含SessionId字段,并根据SessionId找到相对应的Session,从而判断客户端的状态。
可以看出,SessionId是Cookie和Session间的桥梁。
Token是访问资源时所需要的凭证,SessionId也可以看做是一种Token,只不过这种Token存在以下问题:
一是不容易实现横向扩展。比如存在服务器A和服务器B,默认情况下,服务器A与服务器B各自维护自己的Session,在做负载均衡时,如果客户端登录过服务器A,在服务器A中会存储相关的Session信息,而在服务器B中则没有。如果需要实现该功能,即服务器B中可以获取服务器A的Session信息,需要对服务器进行相关设置,保持两个服务器的Session同步。
二是无法实现跨域。受Cookie的限制,某一域名下的Cookie只能用于该域名,而不能用于其他域名,同样也限制了服务端的横向扩展。
三是服务器端需要一直占用一段内存区域存储Session。
四是在开发某些移动端应用时,客户端不支持Cookie。
Token的实现原理
- 客户端向服务器端发送登录请求
- 服务器端在收到登录请求后,验证用户名和密码,通过后,根据用户名和密码,再加上失效时间等信息,生成Token
- 服务端将生成的Token返回至客户端,客户端将Token存储在本地
- 客户端再次访问服务端时,在请求中附加上Token信息
- 服务端收到请求后,对Token进行验证,如果有效,则允许访问相关资源。
基于Token的认证方式是一种无状态的认证方式,服务端不需要再耗费内存空间维护Session信息,而是采用计算的方式验证token的有效性。
2.Token 与 JWT
个人认为,JWT是token认证的一种实现方式。JWT全称JSON Web Token。 JWT通常为:aaaa.bbbbbb.ccccc形式的字符串。
其中aaaa为jwt的头部分,是一个描述jwt元数据的json对象,通常为:
{
"alg": "HS256",
"typ": "JWT"
}
alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256),typ属性表示令牌的类型,JWT令牌统一写为JWT。
bbbb为jwt的payload部分,也是一个json对象,除了默认要添加的数据外,还有7个字段可供选择,分别是分别是,iss:发行人、exp:到期时间、sub:主题、aud:用户、nbf:在此之前不可用、iat:发布时间、jti:JWT ID用于标识该JWT。
cccc为jwt的signature部分,通过服务器端设定的秘钥,根据header中设置的哈希算法,对header和payload部分进行哈希计算,得到signature。
服务器端在接收到jwt后,根据同样的哈希算法和秘钥,对jwt进行校验,通过后读取其中的payload信息,进行认证。
3.JWT的使用(maven)
首先,引入maven依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
然后创建工具类jwtUtil,用于创建和解析jwt
@Component
public class JWTUtil {
@Value("${jwt.secretKey}")
private String secretKey;
public String createJWT(String id,String subject,long ttlMillis,Map<String,Object> params){
JwtBuilder builder= Jwts.builder().
setId(id).
setSubject(subject).
setIssuedAt(new Date()).
signWith(SignatureAlgorithm.HS256,secretKey).
compressWith(CompressionCodecs.DEFLATE);
if(!CollectionUtils.isEmpty(params)){
builder.setClaims(params);
}
if(ttlMillis>0){
builder.setExpiration(new Date(System.currentTimeMillis()+ttlMillis));
}
return builder.compact();
}
public Claims parseJWT(String jwtString){
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtString).getBody();
}
}
接着在application.yml配置文件配置
jwt:
secretKey: abcedfg
然后创建一个响应体
public class JWTResponse {
private String code;
private String msg;
private String jwtData;
public static JWTResponse success(String jwtData){
return new JWTResponse("1","success",jwtData);
}
public static JWTResponse fail(String jwtData){
return new JWTResponse("0","fail",jwtData);
}
public JWTResponse(String code, String msg, String jwtData) {
this.code = code;
this.msg = msg;
this.jwtData = jwtData;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getJwtData() {
return jwtData;
}
public void setJwtData(String jwtData) {
this.jwtData = jwtData;
}
}
接着创建一个controller对上述功能进行测试
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Autowired
private JWTUtil jwtUtil;
@RequestMapping(value = "/login")
public JWTResponse login(@RequestParam(name = "username") String username, @RequestParam(name = "password") String password, HttpServletResponse response){
String jwtString="";
if(username.equals("wangying") && password.equals("123456")){
String id=UUID.randomUUID().toString();
String subject="login subject";
Map<String,Object> params=new HashMap<>();
params.put("name","wangying");
jwtString=jwtUtil.createJWT(id,subject,0,params);
response.addHeader("str",jwtString);
return JWTResponse.success(jwtString);
}else{
return JWTResponse.fail(jwtString);
}
}
@RequestMapping("/index")
public Map<String,Object> index(@RequestParam(value = "jwt") String jwt){
Claims claims=jwtUtil.parseJWT(jwt);
Object username=claims.get("name");
Map<String,Object> map=new HashMap<>();
map.put("username",username);
return map;
}
}
向服务端发送请求
http://127.0.0.1:8080/user/login?username=wangying&password=123456
可以得到如下jwt
"code": "1",
"msg": "success",
"jwtData": "eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslIqT8xLr8zMS1eqBQAAAP__.W_JmTHVBG6UGr4glDtsej51iIFrEGI9Wl194IKYM6X0"
}
再次向服务端发送请求时,带上jwt参数如下:
http://127.0.0.1:8080/user/index?jwt=eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslIqT8xLr8zMS1eqBQAAAP__.W_JmTHVBG6UGr4glDtsej51iIFrEGI9Wl194IKYM6X0
服务端对jwt进行验证,通过后即可访问相关资源