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 生 ...
随机推荐
- Tomcat配置Gizp 客户端使用okHttp3
找到tomcat 在 server.xml 新增如下配置 <Connector connectionTimeout="20000" port="8088" ...
- 用python you-get下载视频
安装python3后 安装you-get包: pip3 install you-get 下载视频: 打开windows终端:运行 you-get url 查看视频信息: you-get -i url ...
- spring boot:shardingsphere+druid整合seata分布式事务(spring boot 2.3.3)
一,shardingshpere为什么要整合seata? 分库分表是数据库扩展中最常用的处理方法, shardingshpere作为使用最广泛的分表中间件, 如果不支持分布式事务,则它的数据一致性就会 ...
- centos8平台给sudo配置日志
一,sudo日志的用途: 我们可以记录下来用户账号在哪个时间进行过sudo 这样不需要再从secure日志中查找用户的sudo记录 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://w ...
- TimeSpan时间间隔详解
1 public string GetShiXian(string fadanshijian) 2 { 3 string result,chulishijian; 4 5 DateTime fdTim ...
- Redis五种常用数据类型
string 字符串常用操作 1.存入字符串键值对 SET key value 2.批量存储字符串键值对 MSET key value [key value ...] 3.获取一个字符串键值 G ...
- Stream(四)
public class Test { /* * 创建:一步 * 中间:0~n步 * 终结:一步 * * 三.终结操作 * 1.void forEach(Consumer ):遍历流中的数据 * 2. ...
- Spring Shiro配置第三方SSO客户端登录
经过实践的Shiro配置,利用 sSOInterceptor 进行sso登录拦截 配置 @Configuration public class ShiroConfiguration extends B ...
- C#/WPF 使用的Task线程程序缺依旧响应缓慢问题
问题:C#/WPF 使用的Task线程程序缺依旧响应缓慢问题 摘要:相信很多做WPF的开发者在请求接口的时候会去采用开一个线程Task去请求接口.避免UI卡主等等.但有一个问题是但开的线程比较多的情况 ...
- 欧拉计划第十一题--java实现
先做个说明,里面所有的题都是我自己写的解题报告,由于我的能力有限,是个刚学java的小白,有很多不足的地方,还望各位大佬不奢赐教,谢谢! Largest product in a grid In th ...