web安全通信之JWT简介
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的依赖即可:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
创建签名秘钥
Key key = MacProvider.generateKey(); String compactJws = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS512, key).compact();
验证JWT
assert Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getSubject().equals("Joe");
示例
下面通过一个示例代码来说明如何使用。示例程序基于springboot。
本例代码在:https://gitee.com/qincd/my-test-projects下的jwt-demo模块。
jwt的操作工具类:
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校验实体类:
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; } }
常量类:
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/*请求
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:
@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:
@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应用启动类:
@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:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<c:set var="ctx" value="${pageContext.request.contextPath}"/>
<html>
<head>
<title>用户登录</title>
</head>
<body>
<div id="loginDiv">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="button" value="登录" onclick="login();">
</div>
<script type="text/javascript" src="${ctx}/js/jquery.js"></script>
<script>
function login() {
var t = {
username:$('input[name=username]').val(),
password:$('input[name=password]').val()
};
if (t.username == '' || t.password== '') {
alert('参数不全');
return;
}
$.post('${ctx}/login',t, function(r) {
if (r.code == 0) {
location.replace('${ctx}/api/test?token='+ r.token);
}
else {
alert(r.msg);
}
});
}
</script>
</body>
</html>
测试:
进入登录页面登录
这里用户名和秘密为admin和123456
输入后,点登录按钮,会进入到/api/test页面。
上面示例代码中token的有效期为5分钟,在5分钟内,可以在任何其他机器或浏览器输入登录后的地址,可以拿到返回结果。
如果超过5分钟再访问则会提示token过期。
示例工程参考:Web安全通讯之JWT的Java实现、JWT 进阶 – JJWT、JWT的Java使用 (JJWT)、你的JWTs存储在哪里、JWT(JSON WEB TOKEN)框架 JJWT 教程、jjwt的github地址
还没有评论,来说两句吧...