zoukankan      html  css  js  c++  java
  • SpringBoot整合JWT实战详解

    jwt 介绍就不多说了,下面通过代码演示开发过程中jwt 的使用。

    (1)在pom.xml中引入对应的jar

    <dependency>
         <groupId>io.jsonwebtoken</groupId>
         <artifactId>jjwt</artifactId>
         <version>0.7.0</version>
     </dependency>

    (2)引入jwt 工具类:token的生成以及获取对应的token信息

    /**
    * @author : wl
    * @Description :
    * @date : 2020/7/3 13:25
    */
    public class JwtUtil {

    public static final String AUTHORIZATION_SECRET = "wlcoder";
    private static final String UID = "uid";
    private static final String USERNAME = "username";
    private static final String PASSWORD = "password";
    private static final String STATUS = "status";

    //创建秘钥
    public static Key getKeyInstance() {
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    byte[] bytes = DatatypeConverter.parseBase64Binary(AUTHORIZATION_SECRET);
    return new SecretKeySpec(bytes, signatureAlgorithm.getJcaName());
    }

    /**
    * 生成token的方法
    *
    * @param user
    * @param expire
    * @return
    */
    public static String generatorToken(SysUser user, int expire) {
    return Jwts.builder().claim(UID, user.getId())
    .claim(USERNAME, user.getUsername())
    .claim(PASSWORD, user.getPassword())
    .claim(STATUS, user.getStatus())
    .setExpiration(DateTime.now().plusSeconds(expire).toDate())
    .signWith(SignatureAlgorithm.HS256, getKeyInstance())
    .compact();
    }

    /**
    * 根据token获取token中的信息
    *
    * @param token
    * @return
    */
    public static SysUser getTokenInfo(String token) {
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
    Claims claims = claimsJws.getBody();
    return SysUser.builder().id((Integer) claims.get(UID))
    .username((String) claims.get(USERNAME))
    .password((String) claims.get(PASSWORD))
    .status((Integer) claims.get(STATUS))
    .build();

    }
    }

    (3)添加注解 @NeedToken,@SkipToken ,加在方法上灵活处理对应的请求

    /**
     * @author : wl
     * @Description : 需要token 验证
     * @date : 2020/7/3 11:40
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NeedToken {
        boolean required() default true;
    }
    /**
     * @author : wl
     * @Description :跳过token 验证
     * @date : 2020/7/3 11:39
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SkipToken {
        boolean required() default true;
    }

    (4)添加拦截器,验证前端请求是否需要token

     
    /**
     * @author : wl
     * @Description :方法请求拦截
     * @date : 2020/7/3 11:47
     */
    @Slf4j
    public class AuthenticationInterceptor implements HandlerInterceptor {
        @Autowired
        private SysUserService userService;
        @Autowired
        private RedisUtil redisUtil;
     
        @Override
        public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws ServletException, IOException {
            String token = httpServletRequest.getHeader("token");
            if (!(object instanceof HandlerMethod)) {
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod) object;
            Method method = handlerMethod.getMethod();
            //检查有没有跳过token的注解
            if (method.isAnnotationPresent(SkipToken.class)) {
                SkipToken skipToken = method.getAnnotation(SkipToken.class);
                if (skipToken.required()) {
                    log.info("该请求无须token验证。。。");
                    return true;
                }
            }
            //检查有没有需要token的注解
                if (method.isAnnotationPresent(NeedToken.class)) {
                    NeedToken needToken = method.getAnnotation(NeedToken.class);
                    if (needToken.required()) {
                        log.info("该请求需要token验证。。。");
                        if (Objects.isNull(token)) {
                            throw new BaseException("无token,请重新登录");
                        }
                        try {
                            JwtUtil.getTokenInfo(token);
                        } catch (ExpiredJwtException e) {
                            throw new BaseException("token超时");
                        }
    //                    SysUser user = userService.findUser(sysUser.getUsername(), sysUser.getPassword());
    //                    if (Objects.isNull(user)) {
    //                        throw new BaseException("用户不存在,请重新登录");
    //                    }
                        if (!Objects.equals(token, redisUtil.get("ms_notify_token"))) {
                            throw new BaseException("token异常,请重新登录");
                        }
                    }
                }
            return true;
        }
     
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        }
     
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        }
    }

    (5)添加拦截器异常处理 :出现异常直接跳转到登录页面 ,这里有个坑,前端为ajax请求时候 使用转发或者重定向会失效。因此处理为 判断请求为ajax请求则设置返回一个状态码如:httpServletResponse.setStatus(666); 前端jquery.js中统一判断处理。

    /**
     * @author : wl
     * @Description : 拦截异常处理
     * @date : 2020/7/5 15:30
     */
    @Slf4j
    public class MyWebHandlerException implements HandlerExceptionResolver {
        @SneakyThrows
        @Override
        public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
            log.info("请求出现异常:" + e.getMessage());
            e.printStackTrace();
    //        return new ModelAndView("redirect:/login");
            ModelAndView modelAndView = new ModelAndView();
            String type = httpServletRequest.getHeader("X-Requested-With");
            if (Objects.equals(type, "XMLHttpRequest")) {
                //是ajax请求
                httpServletResponse.setStatus(666);
                httpServletResponse.setContentType("text/javascript; charset=utf-8");
                httpServletResponse.getWriter().write(e.getMessage());
                return modelAndView;
            } else {
                modelAndView.setViewName("/login");
                return modelAndView;
            }
        }
    }

    jquery.js中添加对应状态码处理:

    //自定义异常信息: 跳转到登录页面
        jQuery.ajaxSetup({
            statusCode:{
                666:function(data){
                    alert(data.responseText);
                    window.location.href="/login";
                }
            }
        })

    (6) 添加webConfig配置 实现WebMvcConfigurer,引入自定义的token拦截以及异常处理

    /**
     * @author : wl
     * @Description :web拦截器
     * @date : 2020/7/3 11:46
     */
    @Configuration
    public class webConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**");
        }
     
        @Bean
        public AuthenticationInterceptor authenticationInterceptor() {
            return new AuthenticationInterceptor();
        }
     
        @Bean
        public WebMvcConfigurer webMvcConfigurer() {
            WebMvcConfigurer adapter = new WebMvcConfigurer() {
                @Override
                public void addViewControllers(ViewControllerRegistry registry) {
                    registry.addViewController("/").setViewName("login");
                    registry.addViewController("/index.html").setViewName("login");
                }
            };
            return adapter;
        }
        /*
         * 异常拦截处理
         * */
        @Override
        public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
            resolvers.add(new MyWebHandlerException());
        }
    }

    到这里基本配置基本差不多了,web应用中怎么处理呢?例如登录功能:

    登录后端代码:

     
    /**
     * @author : wl
     * @Description :
     * @date : 2020/7/2 16:46
     */
    @Controller
    public class Login {
        @Autowired
        private SysUserService sysUserService;
        @Autowired
        private RedisUtil redisUtil;
     
        @SkipToken
        @RequestMapping("/login")
        public String toLogin() {
            System.out.println("跳转到登录页面");
            return "login";
        }
     
        @SkipToken
        @RequestMapping("/index")
        public String toIndex() {
            System.out.println("跳转到主页面");
            return "index";
        }
     
        @SkipToken
        @ResponseBody
        @RequestMapping("/loginIn")
        public ResultUtil loginIn(String username, String password) {
            try {
                SysUser user = sysUserService.findUser(username, password);
                if (null != user) {
                    user.setPassword(password);
                    String token = JwtUtil.generatorToken(user, 60*60);
                    //token 保存在redis中
                    redisUtil.set("ms_notify_token", token);
                    return ResultUtil.ok().data("msg", token).message("登录成功");
                } else {
                    return ResultUtil.error().data("msg", "error").message("用户不存在");
                }
            } catch (BaseException e) {
                return ResultUtil.error().data("msg", e.getMessage()).message("登陆失败");
            }
        }
     
    }

    登录前端代码:这里需要注意的是 配置window.location.href 不生效 需要检查是否设置为form 表单。

    前端接受到token存储在localStorage: localStorage.setItem("token",data.data.msg);

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="description" content="">
        <meta name="author" content="">
        <title>登录</title>
        <link href="css/bootstrap.min.css" rel="stylesheet">
        <link href="css/signin.css" rel="stylesheet">
    </head>
    <body class="text-center">
    <div class="form-signin">
        <img class="mb-4" th:src="@{img/my.svg}" alt="" width="80" height="80">
        <h1 class="h3 mb-3 font-weight-normal">请登录</h1>
        <label class="sr-only">用户名</label>
        <input type="text" name="username" id="username" class="form-control" th:placeholder="用户名" required="required" autofocus="">
        <p class=""></p>
        <label class="sr-only">密码</label>
        <input type="password" name="password" id="password" class="form-control" th:placeholder="密码" required="required" >
        <button class="btn btn-lg btn-primary btn-block" th:onclick="loginIn()">登录</button>
    </div>
    </body>
     
    <script type="text/javascript" src="/js/jquery-3.5.1.js"></script>
    <script type="text/javascript" src="/js/bootstrap.min.js"></script>
    <script>
        function loginIn() {
            debugger;
            var username = $('#username').val();
            var password = $('#password').val();
            if(""==username || ""==password){
                alert("用户信息不完整,请检查!");
                return;
            }
            $.ajax({
                url: '/loginIn',
                type: "post",
                data: {'username': username, 'password': password},
                success: function (data) {
                    if (data.success) {
                        //alert(data.message);
                        localStorage.setItem("token",data.data.msg);
                        location.href = "/index"; //  window.location.href 不生效 检查是否为form 表单
                    } else {
                        alert(data.message)
                    }
                },
                error: function () {
                    alert("登录失败")
                }
            });
        }
    </script>
    </html>

    若是需要token验证,前端对应的ajax 请求需要加上headers 如:

      //禁用,启用
        function disable_config(nid, status) {
            $.ajax({
                url: '/notify/updateStatus',
                data: {'nid': nid, 'status': status},
                type: "post",
                headers: {"token": localStorage.getItem("token")},
                success: function (data) {
                    if (data.success) {
                        location.reload();
                        alert(data.message);
                    } else {
                        alert(data.message + ":" + data.data.msg)
                    }
                }
            });
        }

    后端对应方法上需要添加注解@NeedToken  如:

       /**
         * 禁用 、启用
         */
        @NeedToken
        @ResponseBody
        @SysLogAnnotation("禁用 、启用 配置")
        @RequestMapping(value = "/updateStatus")
        public ResultUtil updateStatus(HttpServletRequest request, String nid, int status) {
            String config_status = (status == 1 ? "启用" : "禁用");
            try {
                notifyConfigService.updateStatus(nid, status);
            } catch (BaseException e) {
                return ResultUtil.error().data("msg", e.getMessage()).message(config_status + "配置失败");
            }
            return ResultUtil.ok().data("msg", "success").message(config_status + "配置成功");
        }

    设计流程基本如上述代码所示,详细代码可以参考 github地址 : https://github.com/wlcoder/ms-notify

    该项目主要是实现自动配置定时发送邮件信息,如果觉得不错,可以star一下。有什么意见或者建议,可以指出来,大家相互学习,谢谢!

  • 相关阅读:
    spark streaming 概述
    spark sql 的性能调优
    LeetCode 106. Construct Binary Tree from Inorder and Postorder Traversal (用中序和后序树遍历来建立二叉树)
    LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal (用先序和中序树遍历来建立二叉树)
    LeetCode 90. Subsets II (子集合之二)
    LeetCode 88. Merge Sorted Array(合并有序数组)
    LeetCode 81. Search in Rotated Sorted Array II(在旋转有序序列中搜索之二)
    LeetCode 80. Remove Duplicates from Sorted Array II (从有序序列里移除重复项之二)
    LeetCode 79. Word Search(单词搜索)
    LeetCode 78. Subsets(子集合)
  • 原文地址:https://www.cnblogs.com/wlong-blog/p/13563928.html
Copyright © 2011-2022 走看看