web安全通信之JWT简介

╰+哭是因爲堅強的太久メ 2022-05-11 08:48 247阅读 0赞

jwt简介

JWT是JSON Web Token的简称,它是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
和Cookie-Session的模式不同,JWT使用Token替换了SessionID的资源访问和状态保持。

jwt的组成

1.Header: 标题包含了令牌的元数据,并且在最小包含签名和/或加密算法的类型
2.Claims: Claims包含您想要签署的任何信息
3.JSON Web Signature (JWS): 在header中指定的使用该算法的数字签名和声明

jwt的认证过程

1.用户登录系统;
2.服务端验证,将认证信息通过指定的算法(例如HS256)进行加密,例如对用户名和用户所属角色进行加密,加密私钥是保存在服务器端的,将加密后的结果发送给客户端,加密的字符串格式为三个”.” 分隔的字符串 Token,分别对应头部、载荷与签名,头部和载荷都可以通过 base64 解码出来,签名部分不可以;
3.客户端拿到返回的 Token,存储到 local storage 或本地数据库;
4.下次客户端再次发起请求,将 Token 附加到 header 中;
5.服务端获取header中的Token,通过相同的算法对Token中的用户名和所属角色进行相同的加密验证,如果验证结果相同,则说明这个请求是正常的,没有被篡改。这个过程可以完全不涉及到查询 Redis 或其他存储;

jjwt

JJWT是一个提供端到端的JWT创建和验证的Java库。
JJWT的目标是最容易使用和理解用于在JVM上创建和验证JSON Web令牌(JWTs)的库。
JJWT是基于JWT、JWS、JWE、JWK和JWA RFC规范的Java实现。

jjwt安装

在pom.xml中配置jjwt的依赖即可:

  1. <dependency>
  2. <groupId>io.jsonwebtoken</groupId>
  3. <artifactId>jjwt</artifactId>
  4. <version>0.9.0</version>
  5. </dependency>

创建签名秘钥

  1. Key key = MacProvider.generateKey(); String compactJws = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS512, key).compact();

验证JWT

  1. assert Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getSubject().equals("Joe");

示例

下面通过一个示例代码来说明如何使用。示例程序基于springboot。
本例代码在:https://gitee.com/qincd/my-test-projects下的jwt-demo模块。

jwt的操作工具类:

  1. public class TokenMgr { public static SecretKey generalKey() throws Base64DecodingException { byte[] encodedKey = Base64.decode(Constant.JWT_SECERT); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 签发JWT * @param id * @param subject * @param ttlMillis * @return * @throws Exception */ public static String createJWT(String id, String subject, long ttlMillis) throws Base64DecodingException { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder() .setId(id) .setSubject(subject) .setIssuedAt(now) .signWith(signatureAlgorithm, secretKey); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); builder.setExpiration(expDate); } return builder.compact(); } /** * 验证JWT * @param jwtStr * @return */ public static CheckResult validateJWT(String jwtStr) { CheckResult checkResult = new CheckResult(); Claims claims = null; try { claims = parseJWT(jwtStr); checkResult.setSuccess(true); checkResult.setClaims(claims); } catch (ExpiredJwtException e) { checkResult.setErrCode(Constant.JWT_ERRCODE_EXPIRE); checkResult.setSuccess(false); } catch (SignatureException e) { checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL); checkResult.setSuccess(false); } catch (Exception e) { checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL); checkResult.setSuccess(false); } return checkResult; } /** * * 解析JWT字符串 * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }

jwt校验实体类:

  1. public class CheckResult { private boolean success; private Claims claims; private String errCode; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public Claims getClaims() { return claims; } public void setClaims(Claims claims) { this.claims = claims; } public String getErrCode() { return errCode; } public void setErrCode(String errCode) { this.errCode = errCode; } }

常量类:

  1. public class Constant { public static final String JWT_SECERT = "security"; public static final String JWT_ERRCODE_EXPIRE = "expire"; public static final String JWT_ERRCODE_FAIL = "fail"; }

JwtFilter:用于拦截/api/*请求

  1. public class JwtFilter extends GenericFilter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // jwt验证 HttpServletRequest request = (HttpServletRequest) servletRequest; // 1.从Cookie获取jwt String token = getTokenFromCookie(request); if (StringUtils.isEmpty(token)) { // 2.从headers中获取 token = request.getHeader("Authorization"); } if (StringUtils.isEmpty(token)) { // 3.从请求参数获取 token = request.getParameter("token"); } if (StringUtils.isEmpty(token)) { throw new ServletException("Missing or invalid token."); } CheckResult checkResult = TokenMgr.validateJWT(token); if (checkResult.isSuccess()) { request.setAttribute("claims",checkResult.getClaims()); filterChain.doFilter(servletRequest,servletResponse); } else { if (checkResult.getErrCode().equals(Constant.JWT_ERRCODE_EXPIRE)) { servletResponse.setCharacterEncoding("utf-8"); PrintWriter printWriter = servletResponse.getWriter(); printWriter.write("token过期,请重新登录"); printWriter.flush(); printWriter.close(); } else if (checkResult.getErrCode().equals(Constant.JWT_ERRCODE_FAIL)) { PrintWriter printWriter = servletResponse.getWriter(); servletResponse.setCharacterEncoding("utf-8"); printWriter.write("token验证失败!"); printWriter.flush(); printWriter.close(); } } } private String getTokenFromCookie(HttpServletRequest request) { String token = null; Cookie[] cookies = request.getCookies(); int len = null == cookies ? 0:cookies.length; if (len > 0) { for (int i=0;i<cookies.length;i++) { Cookie cookie = cookies[i]; if (cookie.getName().equals("token")) { token = cookie.getValue(); break; } } } return token; } @Override public void destroy() { } }

LoginController:

  1. @Controller public class LoginController { @RequestMapping(value = "/login",method = RequestMethod.GET) public String toLogin() { return "/login"; } @RequestMapping(value = "/login",method = RequestMethod.POST) @ResponseBody public Map<String,String> login(String username,String password,HttpServletResponse response) { Map<String,String> result = new HashMap<String, String>(2); if (username.equals("admin") && password.equals("123456")) { String id= UUID.randomUUID().toString(); String subject = "{role:1,permission:[1,2,3]}"; try { String token = TokenMgr.createJWT(id,subject,5*60*1000L); result.put("token",token); result.put("code","0"); } catch (Base64DecodingException e) { result.put("code","1"); result.put("msg", "内部错误!"); } } else { result.put("code","1"); result.put("msg","用户名或密码错误!"); } return result; } }

ApiController:

  1. @Controller @RequestMapping("/api") public class ApiController { @RequestMapping(value = "/test",method = RequestMethod.GET) @ResponseBody public Map<String,Object> test() { Map<String,Object> map = new HashMap<String, Object>(4); map.put("id",10000L); map.put("name","张三"); map.put("age",99); return map; } }

SpringBoot应用启动类:

  1. @SpringBootApplication public class WebApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(WebApplication.class); } //过滤器 @Bean public FilterRegistrationBean jwtFilter() { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new JwtFilter()); registrationBean.addUrlPatterns("/api/*"); return registrationBean; } public static void main( String[] args ) { SpringApplication.run(WebApplication.class, args); } }

增加对jwtFilter的配置。

login.jsp:

  1. <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
  2. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  3. <c:set var="ctx" value="${pageContext.request.contextPath}"/>
  4. <html>
  5. <head>
  6. <title>用户登录</title>
  7. </head>
  8. <body>
  9. <div id="loginDiv">
  10. 用户名:<input type="text" name="username"><br>
  11. 密码:<input type="password" name="password"><br>
  12. <input type="button" value="登录" onclick="login();">
  13. </div>
  14. <script type="text/javascript" src="${ctx}/js/jquery.js"></script>
  15. <script>
  16. function login() {
  17. var t = {
  18. username:$('input[name=username]').val(),
  19. password:$('input[name=password]').val()
  20. };
  21. if (t.username == '' || t.password== '') {
  22. alert('参数不全');
  23. return;
  24. }
  25. $.post('${ctx}/login',t, function(r) {
  26. if (r.code == 0) {
  27. location.replace('${ctx}/api/test?token='+ r.token);
  28. }
  29. else {
  30. alert(r.msg);
  31. }
  32. });
  33. }
  34. </script>
  35. </body>
  36. </html>

测试:
进入登录页面登录
40723692.jpg
这里用户名和秘密为admin和123456

输入后,点登录按钮,会进入到/api/test页面。
92104964.jpg
上面示例代码中token的有效期为5分钟,在5分钟内,可以在任何其他机器或浏览器输入登录后的地址,可以拿到返回结果。
76932439.jpg

如果超过5分钟再访问则会提示token过期。
96939779.jpg

示例工程参考:Web安全通讯之JWT的Java实现、JWT 进阶 – JJWT、JWT的Java使用 (JJWT)、你的JWTs存储在哪里、JWT(JSON WEB TOKEN)框架 JJWT 教程、jjwt的github地址

发表评论

表情:
评论列表 (有 0 条评论,247人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JWT(JSON Web Token)原理简介

    原理说的非常清楚。总结如下: 首先这个先说这个东西是什么,干什么用的,一句话说:就是这是一种认证机制,让后台知道请求是来自于受信的客户端。 那么从这个角度而言,这个东西跟浏

    相关 JWT简介

    一.什么是JWT   Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准.该token被设计为紧凑且安全的,特别适

    相关 jwt 简介

    Jwt是一种用户授权机制,jwt由服务器端产生 当客户端拥有jwt之后,就相当于客户端拥有了访问服务器资源的权限

    相关 Jwt简介

     JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之