本文将用不到100行Java代码, 教你如何在Spring Boot里面用JWT保护RESTful api.css
源代码在 https://github.com/ZhongjunTian/spring-boot-jwt-demo/basic
打开在线demo网站jontian.com:8080 或者代码运行以后打开localhost:8080,
未登陆以前点击 Call Example Service 返回 401 Unaothorized 错误.前端
登陆以后便可获得正确结果java
了解JWT的同窗能够跳过这一部分git
废话少说, 咱们先看看什么是JWT. JSON Web Token其实就是一个包含认证数据的JSON, 大概长这样子
分三个部分,
第一部分{"alg":"HS512"}
是签名算法
第二部分 {"exp":1495176357,"username":"admin"}
是一些数据(你想放什么均可以), 这里有过时日期和用户名
第三部分')4'7�6-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l
很是重要,是签名Signiture, 服务器会验证这个以防伪造. 由于JWT实际上是明文传送, 任何人都能篡改里面的内容. 服务端经过验证签名, 从而肯定这个JWT是本身生成的.github
原理也不是很复杂, 我用一行代码就能表示出来
首先咱们将JWT第一第二部分的内容, 加上你的秘钥(key或者叫secret), 而后用某个算法(好比hash算法)求一下, 求得的内容就是你的签名. 验证的时候只须要验证你用JWT算出来的值是否等于JWT里面的签名.
由于别人没有你的key, 因此也就无法伪造签名.算法
int signiture = ("{alg:HS512}{exp:1495176357,username:admin}" + key).hashCode();
{"alg":"HS512"}{"exp":1495176357,"username":"admin"} ')4'7�6-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l
为了方便复制和使用, 一般咱们都是把JWT用base64编码以后放在http的header里面, 而且每一次呼叫api都附上这个JWT, 而且服务器每次也验证JWT是否过时spring
Base64编码后: eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE0OTUxNzYzNTcsInVzZXJuYW1lIjoiYWRtaW4ifQ.mQtCfLKfI0J7c3HTYt7kRN4AcoixiUSDaZv2ZKOjq2JMZjBhf1DmE0Fn6PdEkyJZhYZJTMLaIPwyR-uu6BMKGw
整个demo一共有三个class
Application.java JwtAuthenticationFilter.java 和 JwtUtil.javaapi
第一步建立一个hello world api安全
@GetMapping("/protected") public @ResponseBody Object hellWorld() { return "Hello World! This is a protected api"; }
第二步建立一个 login的api, 咱们会验证用户的密码, 若是正确, 那么咱们会返回生成jwt. 这时前端拿到的这个jwt就相似于拿到了一个临时的密码, 以后全部的HTTP RESTful api请求都附上这个"临时密码"便可.(专业术语叫令牌/token)ruby
@PostMapping("/login") public Object login(HttpServletResponse response, @RequestBody final Account account) throws IOException { if(isValidPassword(account)) { String jwt = JwtUtil.generateToken(account.username); return new HashMap<String,String>(){{ put("token", jwt); }}; }else { return new ResponseEntity(HttpStatus.UNAUTHORIZED); } }
登陆效果以下图
最后咱们再注册一个检验jwt的过滤器Filter, 经过这个过滤器Filter实现对每一个Rest api请求都验证jwt的功能. 这个JwtAuthenticationFilter继承了OncePerRequestFilter, 任何请求都会先通过咱们的filter, 而后咱们会选择让那些有合法jwt的请求经过咱们的filter.
@Bean public FilterRegistrationBean jwtFilter() { final FilterRegistrationBean registrationBean = new FilterRegistrationBean(); JwtAuthenticationFilter filter = new JwtAuthenticationFilter(); registrationBean.setFilter(filter); return registrationBean; }
这里咱们继承了OncePerRequestFilter, 保证了用户请求任何资源都会运行这个doFilterInternal. 这里咱们会从HTTP Header里面截取JWT, 而且验证JWT的签名和过时时间, 若是有问题, 咱们会返回HTTP 401错误.
PS: 这里有个状况就是用户登陆/login前是没有jwt的, 因此咱们要让登陆的请求
public class JwtAuthenticationFilter extends OncePerRequestFilter { //......一些不重要的代码...... @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { if(isProtectedUrl(request)) { String token = request.getHeader("Authorization"); //检查jwt令牌, 若是令牌不合法或者过时, 里面会直接抛出异常, 下面的catch部分会直接返回 JwtUtil.validateToken(token); } } catch (Exception e) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); return; } //若是jwt令牌经过了检测, 那么就把request传递给后面的RESTful api filterChain.doFilter(request, response); } //咱们只对地址 /api 开头的api检查jwt. 否则的话登陆/login也须要jwt private boolean isProtectedUrl(HttpServletRequest request) { return pathMatcher.match("/**", request.getServletPath()); } private boolean isExceededUrl(HttpServletRequest request) { return pathMatcher.match("/login", request.getServletPath()) || pathMatcher.match("register", request.getServletPath()); } }
这里就两个函数, 第一个函数生成一个有效期1000小时的jwtpublic static String generateToken(String username)
第二个函数是验证JWT是否有效, 若是JWT有效则返回用户名, 不然抛出Exceptionpublic static void validateToken(String token)
这里的代码都很是简洁就十几行, 使用的都是现成的包, 建议直接看源代码.
这就是呼叫api的效果