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一下。有什么意见或者建议,可以指出来,大家相互学习,谢谢!

  • 相关阅读:
    python自动化测试(3)- 自动化框架及工具
    python自动化测试(2)-自动化基本技术原理
    软件开发过程自动化原理及技术(完整示例)
    网络验证码--你到底是爱它还是恨它?
    python的高性能web应用的开发与测试实验
    接口应用小玩具-博客园积分排名变动监控工具
    openwrt-智能路由器hack技术(2)---"网路信息监控和窃取"
    Java中的Date和时区转换
    fastjson JSONObject遍历
    【git】强制覆盖本地代码(与git远程仓库保持一致)
  • 原文地址:https://www.cnblogs.com/wlong-blog/p/13563928.html
Copyright © 2011-2022 走看看