JWT、Token与Session

69 阅读5分钟

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进行验证,通过后即可访问相关资源