自己写的springboot第一个博客项目(三)JWT+Redis

831 阅读5分钟

本文整体内容

将博客项目整合jwt安全登录,加上redis将首页的文章放入缓存提高访问的速度,再将jwt生成的token当做钥匙把当前登录的用户对象放入缓存。再将用户放入UserThreadLocal线程隔离,在使用user对象时获取比较灵活方便。

简单的JWT

​ JWT 之所以能被作为一种声明传递的标准是因为它有自己的结构,并不是随便的发个 token 就可以的,JWT 用于生成 token 的结构有三个部分,使用 . 隔开。

  • Header 头部中主要包含两部分,token 类型和加密算法,如 {typ: "jwt", alg: "HS256"}HS256 就是指 sha256 算法,会将这个对象转成 base64
  • Payload 负载就是存放有效信息的地方,有效信息被分为标准中注册的声明、公共的声明和私有的声明。
  • Signature 这一部分指将 HeaderPayload 通过密钥 secret 和加盐算法进行加密后生成的签名,secret,密钥保存在服务端,不会发送给任何人,所以 JWT 的传输方式是很安全的。
  1. 配置pom

    <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
     </dependency>
    
  2. JWTUtils

      private static long time = 1000*60; //过期时间
        private  static String signtrue = "@@wzj##hahahha"; //加盐
    
        //创建token
        public static String createToken(User user){
            JwtBuilder jwtBuilder = Jwts.builder();
    
            String token = jwtBuilder
                    .setHeaderParam("typ","JWT")//头部  可以不写
                    .setHeaderParam("alg","HS256")
                    .claim("userId","user.getId()")//数据
                    .claim("username",user.getUsername()) 
                    .setExpiration(new Date(System.currentTimeMillis()+time))//有效期
                    .signWith(SignatureAlgorithm.HS256,signtrue)//加密
                    .compact();//合成token
            System.out.println(token);
            return token;
        }
        //检查token
        public static boolean checkToken(String token){
            if(token == null){
                return false;
            }
            try {
                Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signtrue).parseClaimsJws(token);//获得token中的数据,没有就说明过期或者假的
            } catch (Exception e) {
                return false;
            }
            return true;
        }
    }
    
  3. 创建拦截器,别拦截的请求就要携带token来访问.这里简单的jwt我就只验证token是否存在试一下。

    @Component
    public class JWTInterceptor implements HandlerInterceptor {
        
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            String token = request.getHeader("token");
            if(JwtUtils.checkToken(token)){
                return  true;
            }else{
                System.out.println("未登录");
                return false;
            }
        }
    
        @Override
        public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)  {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)  {
    
        }
    }
    
  4. 创建WebConfiguration将拦截器添加进去.这里要注意如果使用swagger,也要swagger的所有路径排除出来。

    @Configuration
    public class WebConfiguration implements WebMvcConfigurer {
    
        //swagger路径
        String[] swaggerExcludes=new String[]{"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui/**"};
    
        @Bean
        JWTInterceptor jwtInterceptor(){
            return new JWTInterceptor();
        }
        //spring拦截器加载在springcontentText之前,所以这里用@Bean提前加载。否则会导致过滤器中的@AutoWired注入为空
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**")//拦截所有
                    .excludePathPatterns(Arrays.asList("/", "/login","/blogs"))//排除的路径
                    .excludePathPatterns(swaggerExcludes);//排除swagger
        }
    
    }
    
  5. 测试

JWT增加redis

我是看的狂神说的redis教程,可以说我很多教程很多都是在这里看的,是个值得关注学习的up主狂神说redis 可以先看1-13和25、26、27集用他话说,这样就是先做那%90的api调用程序员。我用了狂神的封装类ReidsUtil和配置自定义注解,看视频关注公众号可以得到。。希望大家好好学越来越好。

  1. 登录业务层代码更改

    就是将生成的token放在redis中,我这里吧token当做钥匙,存的是use象。因为我觉得这样可以更好获得当前登录用户是谁。

     public Result findByUsername(String username, String password) {
            User user = userMapper.findByUsername(username,password);
            if(user != null) {
                /*****************更改的代码*******************/
                String token = JwtUtils.createToken(user);
                redisUtil.set("token"+token,user,60*5);
                /********************************************/
                return Result.success(token);
            }else{
                //感觉这样写不是很好
                return Result.fail("error");
            }
        }
    
  2. jwt拦截器更改

    很简单就是拿着当前钥匙token去redis找当前登录user的,如果不是为空就是正确的。我这里加了一个线程存放user。

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
            if(!(handler instanceof HandlerMethod)){
                //handler 可能是RequestResourceHandler Springboot 程序 访问静态资源 默认去classpath下的static目录去查
                return true;
            }
            String token = request.getHeader("token");
            //*******************/
            User user = (User)redisUtil.get("token"+token);
            if(user != null){
                UserTheadLocal.put(user) ;
             //****************************//
                return  true;
            }else{
                Result result = Result.fail("未登录");
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().print(JSON.toJSONString(result));
                return false;
            }
        }
    
  3. UserTheadLocal线程

    提高效率,在添加文章等在其他需要获得当前用户信息的情况下效率高。利用线程隔离更加安全。

    public class UserTheadLocal {
    
        private UserTheadLocal(){};
        //线程隔离
        private static  final  ThreadLocal<User> LOCAL = new ThreadLocal<>();
    
        public static  void put(User user){
            LOCAL.set(user);
        }
    
        public  static  User get(){
            return  LOCAL.get();
        }
    
        public static  void remove(){
            LOCAL.remove();
        }
    }
    

其他业务中使用redis

  1. 首页中有查询所有的文章,这里不用想放在redis肯定提高效率

     public Result findAllBlogs(Integer currentPage) {
            Page pageMap = (Page) redisUtil.get("pageMap");
            if (pageMap == null) {
                System.out.println("**********走数据库**************");
                Page page = new Page(currentPage, 5);
                QueryWrapper<Blog> query = new QueryWrapper<Blog>();
                pageMap = blogMapper.selectMapsPage(page, query.orderByDesc("created"));
                String pageList = JSON.toJSONString(pageMap);
                redisUtil.set("pageMap", pageMap, 60 * 5);
            } else {
                System.out.println("***********走Redis************");
            }
            return Result.success(pageMap);
        }
    
  2. 在查询单个文章也加入了redis,但是这个redia有效时间可以短一些

    我这里出现了错误,用的fastJson从数据库中那json数据不能直接封装对象,

    所以用了parseObject反序列化。

    public Blog selectBlog(Long id) {
            String blogJson1 = (String)redisUtil.get("Blog" + id);
            Blog blog = JSON.parseObject(blogJson1,Blog.class);
            if (blog == null) {
                System.out.println("**********走数据库**************");
                blog = blogMapper.selectById(id);
                String blogJson = JSON.toJSONString(blog);
                System.out.println(blogJson);
                redisUtil.set("Blog" + id, blogJson, 60 * 2);
            }else{
                System.out.println("***********走Redis************");
            }
            return blog;
        }
    

解开第二篇中疑惑

疑点.jpg

这里我新加了add接口,当时可能太**,没想到这一点。

  1. blogVo 接收部分关于blog信息即可

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class BlogVo {
    
        private Long id;
    
        @NotBlank(message = "标题不能为空")
        private String title;
    
        @NotBlank(message = "摘要不能为空")
        private String description;
    
        @NotBlank(message = "内容不能为空")
        private String content;
    }
    
    
  2. /add 添加 文章

        @ApiOperation(value = "添加文章")
        @PostMapping("/blog/add")
        public Result add( @RequestBody BlogVo blogVo){
            System.out.println(blogVo);
            Blog blog = new Blog();
            blog.setCreated(LocalDateTime.now());
            blog.setUserId(UserTheadLocal.get().getId());
            blog.setStatus(0);
    //        BeanUtils.copyProperties(blog, blogVo);我这里不能使用 
            blog.setContent(blogVo.getContent());
            blog.setDescription(blogVo.getDescription());
            blog.setTitle(blogVo.getTitle());
            System.out.println(blog);
            boolean edit = blogService.insertBlog(blog);
            if(edit){
                return Result.success(200,"添加成功",null);
            }
            return Result.fail("添加失败");
        }
    
  3. /edit 修改文章

     @ApiOperation(value = "修改文章")
        @PostMapping("/blog/edit")
        public Result edit(@Validated @RequestBody BlogVo blogVo){
            Blog temp = null;
            temp = blogService.selectBlog(blogVo.getId());
            if(temp != null){
                if(UserTheadLocal.get().getId() != temp.getUserId()){
                    return Result.fail("无权限修改");
                }else{
    //                BeanUtils.copyProperties(temp,blogVo);
                    temp.setContent(blogVo.getContent());
                    temp.setDescription(blogVo.getDescription());
                    temp.setTitle(blogVo.getTitle());
                    boolean flag  = blogService.updateBlog(temp);
                    if(flag){
                        return Result.success(200,"修改成功",null);
                    }else{
                        return  Result.fail("修改失败");
                    }
                }
            }
            return  Result.fail("修改失败");
        }
    

总结

jwt并不难,在以后的道路中不要被听到的陌生技术词语给唬到,不就是一个技术,他们敢说出来,我们也能学会,精通他。

redis我也只是学会了皮毛,面向api调用。我很喜欢狂神,因为他的视频有很多鸡汤,这玩意可能对部分人没有用,但我好像很感性呀,看其他视频我会困,看他视频鸡汤给我喂得热血沸腾。redis还得继续精通。

有问题联系

QQ 1819220754