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 生 ...
随机推荐
- 多测师全方位面试题腾讯 _自动化面试题_高级讲师肖sir
作答注意:候选人可以两题都做,也可以两题任选一题做即可. 笔试题一:1.查询 https://www.newsmth.net/nForum/#!board/PieLove2.获取发贴时间是2020年8 ...
- pytest文档45-allure添加环境配置(environment)
前言 在 allure 报告首页 ENVIRONMENT 显示 'There are no environment variables' 没有环境变量的配置信息. 环境变量配置可以添加报告相关的配置参 ...
- ElementUI级联选择器动态加载Demo
嗯,今天项目遇到,弄了一会,这里分享一下,不足之处请小伙伴指出来, 官网Demo: <el-cascader :props="props"></el-cascad ...
- MySQL数据库基础-3
SQL语言 结构化的查询云烟 有国际标准. 非常容易学习的,关注数据本身,类似于shell SQL解释器 命令行效率比较高 应用编程接口 ODBC:Open Database Connectivity ...
- 解决Django本地接口不能跨域访问的问题
1.安装django-cors-headers模块: pip install django-cors-headers 2.插入Django的APP配置中: # 修改settings.py中的INSTA ...
- 3. Distributional Reinforcement Learning with Quantile Regression
C51算法理论上用Wasserstein度量衡量两个累积分布函数间的距离证明了价值分布的可行性,但在实际算法中用KL散度对离散支持的概率进行拟合,不能作用于累积分布函数,不能保证Bellman更新收敛 ...
- 腾讯云函数免费搭建onedrive网盘
目录 腾讯云函数 介绍 真免费? 搭建教程 准备条件 创建云函数 创建触发器 配置SecretId和SecretKey 添加onedriver盘 自定义域名 访问路径处理 总结 搭建期间遇到的问题 想 ...
- 【设计模式】第一篇:概述、耦合、UML、七大原则,详细分析总结(基于Java)
迷茫了一周,一段时间重复的 CRUD ,着实让我有点烦闷,最近打算将这些技术栈系列的文章先暂时搁置一下,开启一个新的篇章<设计模式>,毕竟前面写了不少 "武功招式" 的 ...
- Kubernetes 使用 Ingress 实现灰度发布功能
使用 Ingress 实现灰度发布 一.Canary 规则说明 Ingress-Nginx 是一个K8S ingress工具,支持配置 Ingress Annotations 来实现不同场景下的灰度发 ...
- 【DeepLearning】AlexNet
在前文中,我们介绍了LeNet的相关细节,它是由两个卷积层.两个池化层以及两个全链接层组成.卷积都是5*5的模板,stride =1,池化为MAX.整体来说它有三大特点:局部感受野,权值共享和池化.2 ...