本文整体内容
将博客项目整合jwt安全登录,加上redis将首页的文章放入缓存提高访问的速度,再将jwt生成的token当做钥匙把当前登录的用户对象放入缓存。再将用户放入UserThreadLocal线程隔离,在使用user对象时获取比较灵活方便。
简单的JWT
JWT 之所以能被作为一种声明传递的标准是因为它有自己的结构,并不是随便的发个 token
就可以的,JWT 用于生成 token
的结构有三个部分,使用 .
隔开。
Header
头部中主要包含两部分,token
类型和加密算法,如{typ: "jwt", alg: "HS256"}
,HS256
就是指sha256
算法,会将这个对象转成base64
。Payload
负载就是存放有效信息的地方,有效信息被分为标准中注册的声明、公共的声明和私有的声明。Signature
这一部分指将Header
和Payload
通过密钥secret
和加盐算法进行加密后生成的签名,secret
,密钥保存在服务端,不会发送给任何人,所以 JWT 的传输方式是很安全的。
-
配置pom
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
-
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; } }
-
创建拦截器,别拦截的请求就要携带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) { } }
-
创建
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 } }
-
测试
JWT增加redis
我是看的狂神说的redis教程,可以说我很多教程很多都是在这里看的,是个值得关注学习的up主狂神说redis 可以先看1-13和25、26、27集
用他话说,这样就是先做那%90的api调用程序员。我用了狂神的封装类ReidsUtil和配置自定义注解,看视频关注公众号可以得到。。希望大家好好学越来越好。
-
登录业务层代码更改
就是将生成的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"); } }
-
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; } }
-
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
-
首页中有查询所有的文章,这里不用想放在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); }
-
在查询单个文章也加入了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; }
解开第二篇中疑惑
这里我新加了add接口,当时可能太**,没想到这一点。
-
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; }
-
/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("添加失败"); }
-
/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