JWT认证方案讲解
在介绍JWT,先提一下session认证方案。
session和cookie的验证流程图
Cookie中带的是每个用户独有的sessionID。
session的安全性
session ID生成规则:随机数加时间加上JVM的ID值
每个用户都有自己独有的session ID。
session劫持和防护
session劫持:
sessionID被他人获取并伪造成用户本人。
session防护:
- HttpOnly:不允许前端读取
- Secure:仅支持HTTPS
session的缺点
扩展性差:如果将来搭建了多个服务器,虽然每个服务器都执行的是同样的业务逻辑,但是session数据是保存在内存中的(不是共享的),用户第一次访问的是服务器1,当用户再次请求时可能访问的是另外一台服务器2,服务器2获取不到session信息,就判定用户没有登陆过。
需要服务端存储数据:通常session是存储在内存中的,每个用户通过认证之后都会将session数据保存在服务器的内存中,而当用户量增大时,服务器的压力增大。
JWT
JWT是一种流行的认证方案
JWT原理
在http请求中携带的是左边的经过编码的字符串,服务器会解码成右边的部分。
header.payload.signature
头部(Header) alg指的是签名算法为HS256
消息体(Payload)
签名(Signature):检查消息体是否被篡改过,对信息体的数据进行保护,防止被篡改。
JWT流程
session和JWT对比
session的优缺点
优点:简单、方便
缺点:扩展性差、需要存储数据
JWT的优点
减少存储开销
可扩展性强
同时用于认证和交换信息:JWT直接将信息携带传输。
防止被伪造和篡改:签名机制防止被伪造。
JWT的缺点
默认不加密,不适合保存敏感信息
无法临时废止、登出需要额外处理
有效期不易评估
网络开销相对高
项目实战
首先引入依赖
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.14.0</version></dependency>
userController控制类下的登陆方法:(设置JWT)
"/loginWithJwt") (publicApiRestResponseloginWithJwt(StringuserName, Stringpassword) { if (StringUtils.isEmpty(userName)) { returnApiRestResponse.error(ImoocMallExceptionEnum.NEED_USER_NAME); } if (StringUtils.isEmpty(password)) { returnApiRestResponse.error(ImoocMallExceptionEnum.NEED_PASSWORD); } Useruser=userService.login(userName, password); //保存用户信息时,不保存密码user.setPassword(null); Algorithmalgorithm=Algorithm.HMAC256(Constant.JWT_KEY); Stringtoken=JWT.create() .withClaim(Constant.USER_NAME, user.getUsername()) .withClaim(Constant.USER_ID, user.getId()) .withClaim(Constant.USER_ROLE, user.getRole()) //过期时间 .withExpiresAt(newDate(System.currentTimeMillis() +Constant.EXPIRE_TIME)) .sign(algorithm); returnApiRestResponse.success(token); }
用户过滤器从JWT中获取数据
/*** 描述: 用户过滤器*/publicclassUserFilterimplementsFilter { publicstaticThreadLocal<User>userThreadLocal=newThreadLocal(); publicUsercurrentUser=newUser(); UserServiceuserService; publicvoidinit(FilterConfigfilterConfig) throwsServletException { } publicvoiddoFilter(ServletRequestservletRequest, ServletResponseservletResponse, FilterChainfilterChain) throwsIOException, ServletException { HttpServletRequestrequest= (HttpServletRequest) servletRequest; if ("OPTIONS".equals(request.getMethod())) { filterChain.doFilter(servletRequest, servletResponse); } else { Stringtoken=request.getHeader(Constant.JWT_TOKEN); if (StringUtils.isEmpty(token)) { PrintWriterout=newHttpServletResponseWrapper( (HttpServletResponse) servletResponse).getWriter(); out.write("{\n"+" \"status\": 10007,\n"+" \"msg\": \"NEED_LOGIN\",\n"+" \"data\": null\n"+"}"); out.flush(); out.close(); return; } Algorithmalgorithm=Algorithm.HMAC256(Constant.JWT_KEY); JWTVerifierverifier=JWT.require(algorithm).build(); try { DecodedJWTjwt=verifier.verify(token); currentUser.setId(jwt.getClaim(Constant.USER_ID).asInt()); currentUser.setRole(jwt.getClaim(Constant.USER_ROLE).asInt()); currentUser.setUsername(jwt.getClaim(Constant.USER_NAME).asString()); userThreadLocal.set(currentUser); } catch (TokenExpiredExceptione) { //token过期,抛出异常thrownewImoocMallException(ImoocMallExceptionEnum.TOKEN_EXPIRED); } catch (JWTDecodeExceptione) { //解码失败,抛出异常thrownewImoocMallException(ImoocMallExceptionEnum.TOKEN_WRONG); } filterChain.doFilter(servletRequest, servletResponse); } } publicvoiddestroy() { } }
前端把jwt_token(这个名称需要前后端统一规定)封装在请求头中发给后端。