• SpringBoot实现JWT保护前后端分离RESTful API


    通常情况下, 将api直接暴露出来是非常危险的. 每一个api呼叫, 用户都应该附上额外的信息, 以供我们认证和授权. 而JWT是一种既能满足这样需求, 而又简单安全便捷的方法. 前端login获取JWT之后, 只需在每一次HTTP呼叫的时候添加上JWT作为HTTP Header即可.

    本文将用不到100行Java代码, 教你如何在Spring Boot里面用JWT保护RESTful api.

    源代码在 https://github.com/ZhongjunTian/spring-boot-jwt-demo/basic
    打开在线demo网站jontian.com:8080 或者代码运行之后打开localhost:8080,
    未登录之前点击 Call Example Service 返回 401 Unaothorized 错误.

     
    登录前


    登录之后即可得到正确结果

     
    登陆后

    1. 什么是JWT

    了解JWT的同学可以跳过这一部分

    废话少说, 我们先看看什么是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是自己生成的.

    原理也不是很复杂, 我用一行代码就能表示出来
    首先我们将JWT第一第二部分的内容, 加上你的秘钥(key或者叫secret), 然后用某个算法(比如hash算法)求一下, 求得的内容就是你的签名. 验证的时候只需要验证你用JWT算出来的值是否等于JWT里面的签名.
    因为别人没有你的key, 所以也就没法伪造签名.

    简单粗暴一行代码解释什么是签名:
     int signiture = ("{alg:HS512}{exp:1495176357,username:admin}" + key).hashCode();
    最后附上签名,得到完整的JWT:
    {"alg":"HS512"}{"exp":1495176357,"username":"admin"}    ')4'76-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l

    为了方便复制和使用, 通常我们都是把JWT用base64编码之后放在http的header里面, 并且每一次呼叫api都附上这个JWT, 并且服务器每次也验证JWT是否过期

    通常我们用到的JWT:
    Base64编码后:
    eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE0OTUxNzYzNTcsInVzZXJuYW1lIjoiYWRtaW4ifQ.mQtCfLKfI0J7c3HTYt7kRN4AcoixiUSDaZv2ZKOjq2JMZjBhf1DmE0Fn6PdEkyJZhYZJTMLaIPwyR-uu6BMKGw
    

    2. 三个class实现JWT

    整个demo一共有三个class
    Application.java JwtAuthenticationFilter.java 和 JwtUtil.java

    2.1首先我们看一看Application.java

    第一步创建一个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)

    复制代码
    @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;
        }
    复制代码

    2.2然后我们看一下JwtAuthenticationFilter.java

    这里我们继承了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());
    }
    
    
    }
    复制代码

    2.3最后我们看一下JwtUtil.java

    这里就两个函数, 第一个函数生成一个有效期1000小时的jwt
    public static String generateToken(String username)
    第二个函数是验证JWT是否有效, 如果JWT有效则返回用户名, 否则抛出Exception
    public static void validateToken(String token)
    这里的代码都非常简洁就十几行, 使用的都是现成的包, 建议直接看源代码.

    3.测试

    这就是呼叫api的效果

     
    正确jwt
     
  • 相关阅读:
    spring cache设置指定Key过期时间
    Idea Debug多线程不进断点问题处理
    spring cloud gateway使用 uri: lb://方式配置时,服务名的特殊要求
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1067:整数的个数
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言——1067:整数的个数
    征战蓝桥 —— 2013年第四届 —— C/C++A组第10题——大臣的旅费
    征战蓝桥 —— 2013年第四届 —— C/C++A组第10题——大臣的旅费
    征战蓝桥 —— 2013年第四届 —— C/C++A组第10题——大臣的旅费
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1066:满足条件的数累加
    信息学奥赛一本通(C++)在线评测系统——基础(一)C++语言—— 1066:满足条件的数累加
  • 原文地址:https://www.cnblogs.com/yelanggu/p/11399740.html
Copyright © 2020-2023  润新知