当前位置: 首页 > 后端技术 > Java

用jwt保护你的接口服务

时间:2023-04-01 21:00:53 Java

之前写过一篇关于接口服务规范的文章。token来保证接口服务的安全。今天我们就来说说使用jwt生成token的一种更便捷的方式。1.什么是JWTJSONWebToken(JWT)定义了一种紧凑且自包含的方式,以JSON对象的形式在各??方之间安全地传输信息。此信息可以被验证和信任,因为它是经过数字签名的。JWT可以设置有效期。JWT是一个很长的字符串,包括Header、Playload和Signature,以.分隔。HeadersHeaders部分描述了JWT的基本信息,一般包括签名算法和token类型,数据如下:{"alg":"RS256","typ":"JWT"}PlayloadPlayload是存放有效信息的地方,JWTregulations以下7个字段是推荐但非强制的:iss:jwtissuersub:面向jwt的用户aud:接收jwt的一方exp:jwt过期时间,这个过期时间必须大于发布时间nbf:定义在what之前time,jwt不可用iat:jwt发行时间jti:jwt的唯一标识,主要作为一次性token使用此外,我们还可以自定义内容{"name":"JavaJourney","age":18}SignatureSignature是对JWT前两部分进行加密的字符串,base64对Headers和Playload进行编码,使用Headers中指定的加密算法和密钥进行加密,得到JWT的第三部分。2.JWT生成和解析token在应用服务中引入JWT依赖io.jsonwebtokenjjwt0.9.0根据JWT的定义,生成一个用RSA算法加密的token,有效期为30分钟"age",user.getAge())//rsa加密。signWith(SignatureAlgorithm.RS256,RsaUtil.getPrivateKey(PRIVATE_KEY))//有效期为30分钟。setExpiration(DateTime.now().plusSeconds(30*60).toDate()).compact();}登录界面验证通过后,调用JWT生成一个带有用户ID的token响应给用户。在接下来的请求中,header中携带了用于签名验证的token。签名验证通过后,才能正常访问应用服务。publicstaticClaimsparseToken(Stringtoken)throwsException{returnJwts.parser().setSigningKey(RsaUtil.getPublicKey(PUBLIC_KEY)).parseClaimsJws(token).getBody();}三、token续订问题上面介绍了JWT验证流程,现在我们考虑这样一个问题,客户端携带token访问订单接口,token验证通过,客户端下单成功,返回订单结果,然后客户端携带token调用支付接口进行支付,验证签名后发现token无效怎么办?只能告诉用户token失效了,然后让用户重新登录获取token?这种体验非常糟糕。Oauth2在这方面做得更好。除了发行token,还会发行refresh_token。当token过期时,会调用refresh_token重新获取token。如果refresh_token过期,则再次提示用户。前往登录。现在我们模拟oauth2的实现来完成JWT的refresh_token。思路大概是用户登录成功后,在签发token的同时,生成一个加密的字符串作为refresh_token,将refresh_token存入redis,并设置合理的过期时间(一般是refresh_token的过期时间设置得更长)。然后将token和refresh_token响应给客户端。伪代码如下:@PostMapping("getToken")publicResultBeangetToken(@RequestBodyLoingUseruser){ResultBeanresultBean=newResultBean();//用户信息校验失败,响应错误if(!user){resultBean.fillCode(401,"账号密码错误");返回结果Bean;}字符串标记=null;字符串refresh_token=null;try{//由jwt生成的令牌token=JwtUtil.createToken(user);//刷新令牌refresh_token=Md5Utils.hash(System.currentTimeMillis()+"");//refresh_token过期时间为24小时redisUtils.set("refresh_token:"+refresh_token,token,30*24*60*60);}catch(Exceptione){e.printStackTrace();}Mapmap=newHashMap<>();map.put("access_token",token);map.put("refresh_token",refresh_token);map.put("expires_in",2*60*60);resultBean.fillInfo(地图);returnresultBean;}客户端调用该接口时,在请求头中携带token,在拦截器中拦截请求,并验证token的合法性。如果验证token失败,则去判断是否是redis中的refresh_token请求,如果refresh_token验证也失败,客户端会响应一个验证异常,提示客户端重新登录,伪代码如下:HttpHeadersheaders=request.getHeaders();//获取请求头中的tokenStringtoken=headers.getFirst("Authorization");//判断请求头中是否有tokenif(StringUtils.isEmpty(token)){resultBean.fillCode(401,"认证失败,请携带有效token");returnresultBean;}if(!token.contains("Bearer")){resultBean.fillCode(401,"认证失败,请携带有效token");returnresultBean;}token=token.replace("Bearer","");//如果请求头中有token,则解析tokentry{Claimsclaims=TokenUtil.parseToken(token).getBody();}catch(异常e){e.printStackTrace();StringrefreshToken=redisUtils.get("refresh_token:"+token)+"";if(StringUtils.isBlank(refreshToken)||"null".equals(refreshToken)){resultBean.fillCode(403,"refresh_token已过期,请重新获取令牌");返回结果bean;}}refresh_token交换token的伪代码如下:@PostMapping("refreshToken")publicResultrefreshToken(Stringtoken){ResultBeanresultBean=newResultBean();字符串refreshToken=redisUtils.get(TokenConstants.REFRESHTOKEN+token)+"";字符串access_token=null;尝试{Claimsclaims=JwtUtil.parseToken(refreshToken);Stringusername=claims.get("用户名")+"";Stringpassword=claims.get("密码")+"";登录用户loginUser=newLoginUser();loginUser.setUsername(用户名);loginUser.setPassword(密码);access_token=JwtUtil.createToken(loginUser);}catch(Exceptione){e.printStackTrace();}Mapmap=newHashMap<>();map.put("access_token",access_token);map.put("refresh_token",token);map.put("expires_in",30*60);resultBean.fillInfo(地图);returnresultBean;}通过上面的分析,我们简单的实现了token的发行、验证和更新问题。JWT作为一个轻量级的认证框架,使用起来非常方便,但也存在一些问题。JWT的Playload部分只是base64编码,这样我们的信息其实是完全暴露的。一般情况下,不要在JWT中存储敏感信息。JWT生成的token比较长。每次在请求头中携带token,请求就会被窃取。性能问题。JWT生成后,服务端无法丢弃,只能等待JWT主动过期。下面这段是我在网上看到的关于JWT比较适用的一个场景:有效期短,预计只有一次。具有以下特点:可以识别用户,链接是时效的(通常只允许在几小时内激活),不可篡改激活其他可能的账户,一次性。这种场景适合使用JWT。