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一下。有什么意见或者建议,可以指出来,大家相互学习,谢谢!
SpringBoot整合JWT实战详解的更多相关文章
- Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例
摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! “看看星空,会觉得自己很渺小,可能我们在宇宙中从来就是一个偶然.所以,无论什么事情,仔细想一 ...
- SpringBoot配置文件 application.properties详解
SpringBoot配置文件 application.properties详解 本文转载:https://www.cnblogs.com/louby/p/8565027.html 阅读过程中若发现 ...
- Springboot整合log4j2日志全解
目录 常用日志框架 日志门面slf4j 为什么选用log4j2 整合步骤 引入Jar包 配置文件 配置文件模版 配置参数简介 Log4j2配置详解 简单使用 使用lombok工具简化创建Logger类 ...
- SPRINGBOOT注解最全详解(
# SPRINGBOOT注解最全详解(整合超详细版本) 使用注解的优势: 1.采用纯java代码,不在需要配置繁杂的xml文件 ...
- 前后端分离,简单JWT登录详解
前后端分离,简单JWT登录详解 目录 前后端分离,简单JWT登录详解 JWT登录流程 1. 用户认证处理 2. 前端登录 3. 前端请求处理 4. 后端请求处理 5. 前端页面跳转处理 6. 退出登录 ...
- 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING
<Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th 2014 Email:skyseraph00@163.com 更多精彩请直接 ...
- 011-Scala中的apply实战详解
011-Scala中的apply实战详解 object中的apply方法 class中的apply方法 使用方法 apply方法可以应用在类或者Object对象中 class类 必须要创建实例化的类对 ...
- 010-Scala单例对象、伴生对象实战详解
010-Scala单例对象.伴生对象实战详解 Scala单例对象详解 函数的最后一行是返回值 子项目 Scala伴生对象代码实战 object对象的私有成员可以直接被class伴生类访问,但是不可以被 ...
- 008-Scala主构造器、私有构造器、构造器重载实战详解
008-Scala主构造器.私有构造器.构造器重载实战详解 Scala主构造器实战 无参数的主构造器 分析 1.name 需要赋初值,一般通过占位符来代表空值 2.private 声明私有的age 生 ...
随机推荐
- SQl编程存储过程
过程化存储 存储过程,一组为完成特定功能.经过编译后存储在数据库中的SQL语序集 灵活性:存储过程中可以进行流程控制和循环操作来完成复杂的判断和运算 一致性:通过存储过程可以使一些关联的操作一起发生, ...
- python 递归求和
例子: 1 mylist = [1,2,3,4,5] 2 def func(var): 3 if var == []: 4 print('列表为空') 5 return 0 6 return var[ ...
- go 加锁
package mainimport ( "fmt" "math/rand" "sync" "time")var tic ...
- linux 压缩 tar命令
linux中tar命令用法 总结 *.tar 用 tar –xvf 解压 *.gz 用 gzip -d或者gunzip 解压 *.tar.gz和*.tgz 用 tar –xzf 解压 *.bz2 ...
- linux磁盘空间满了
df-h 看到根目录已经用满了! find / -size +100M |xargs ls -lh列出系统内大于100M的文件 du --max-depth=1 -h 查看当前目录内文件夹的大小 看一 ...
- 小tip:CSS计数器+伪类实现数值动态计算与呈现【转】
[原文]http://www.zhangxinxu.com/wordpress/2014/12/css-counters-pseudo-class-checked-numbers/ 一.CSS计数器为 ...
- Pytest配置文件声明自定义用例标识
使用pytest.ini添加自定义用例标识: [pytest] # 1.使用没有注册过的标记抛出错误 addopts = --strict-markers # 2.自定义标记 markers = sm ...
- MVC实现修改数据
前言 前言 最近没什么好写的,所以写个修改来巩固一下知识 控制器 public ActionResult UpdeteCartsNum(SYS_Carts sYS_Carts) { var list ...
- 556. 下一个更大元素 III
556. 下一个更大元素 III 给定一个32位正整数 n,你需要找到最小的32位整数,其与 n 中存在的位数完全相同,并且其值大于n.如果不存在这样的32位整数,则返回-1. 示例 1: 输入: 1 ...
- 八皇后C++版本
emmmm~刚刚学C++,写一个八皇后,凑合看吧嘤嘤嘤 1 #include <iostream> 2 #include<cstdlib> 3 #include<cmat ...