JWT基础介绍,请访问:一分钟搞懂JWT
SpringBoot整合JWT,本次教程使用JWT场景是用户登录后,服务端生成Jwt Token,并返回至客户端,当客户端再次访问系统时需携带jwt Token,发送请求后,服务端会校验jwt token是否正确则运行客户访问系统,否则提示用户token信息异常,无法访问系统。
其实最终的目的就是通过jwt token 来判断用户是否认证通过系统。
那么在这里我们会思考以下几个问题:
1、当客户进行登录操作,并成功后?我们如何创建jwt Token?
2、如何将jwt token 返回给客户端
3、客户端一般如何携带jwt token 发送至服务端
4、服务端如何验证该jwt token 是否有效?
带着以上几个问题我们开始整合JWT
1)首先在pom.xml中引入jwt的坐标
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
通过以上引入的java-jwt包我们可以方便的对jwt token进行创建和获取
2)创建用户登录的页面login.html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="login">
<h1>登录</h1>
<font color="red" th:text="${msg}"></font>
<form class="layui-form" method="post" action="/user/login">
<div class="layui-form-item">
<input class="layui-input" name="userName" value="admin" placeholder="用户名" type="text"
autocomplete="off">
</div>
<div class="layui-form-item">
<input class="layui-input" name="password" value="" placeholder="密码"
type="password" autocomplete="off">
</div>
<input type="submit" value="登录">
</form>
</div>
</body>
</html>
3)编写后台登录的处理器Controller
package org.learn.controller;
import com.alibaba.fastjson.JSONObject;
import org.learn.jwt.JwtUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 用戶控制器
*
* @return
*/
@RequestMapping("/login")
@ResponseBody
public JSONObject submitLogin(String userName, String password, HttpServletRequest req, HttpServletResponse rep, Model model) throws Exception {
JSONObject jsonObject = new JSONObject();
if (userName.equals("test1") && "123456".equals(password)) {
String token = JwtUtil.createToken("userInfo", userName);//创建token
jsonObject.put("token", token);
} else {
jsonObject.put("msg", "用户名或密码错误!");
}
return jsonObject;
}
}
从login方法中我们可以看到,当用户信息校验成功后,我们就可以创建token了,我们可以封装个JwtUtil用于进行jwt token的管理,具体代码如下
package org.learn.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类,包括jwt的创建、验证等方法
*/
public class JwtUtil {
private static final String SECRET = "org.learnjwt.secret"; //秘钥
private static final String ISSUER = "jwtlearn";
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
/**
* 生成token
*
* @param username 用户名
* @return 返回生成的token字符串
*/
public static String createToken(String infoJson, String username) throws Exception {
int second = 100000; //jwt token有效期 毫秒
Algorithm algorithm = Algorithm.HMAC256(SECRET);
// 附带username信息
return JWT.create()
.withIssuer(ISSUER) //jwt签发者
.withClaim("username", username) //用户名
.withExpiresAt(DateUtils.addSeconds(new Date(), second)) //有效日期
.withSubject(infoJson) //用户信息
.sign(algorithm);//签名算法
}
/**
* 校验token是否正确
*
* @param token TOKEN
* @return boolean
*/
public static Map verify(String token, String username) {
Map result = new HashMap<String, Object>(2);
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("username", username)
.build();
DecodedJWT jwt = verifier.verify(token);
String subject = jwt.getSubject();
result.put("username", username);
result.put("isSuccess", true);
result.put("ex", null);
result.put("subject", subject);
return result;
} catch (Exception e) {
logger.info("鉴权失败", e);
result.put("isSuccess", false);
result.put("exception", e);
}
return result;
}
/**
* 获得token的到期时间
*
* @return token中包含的签发时间
*/
public static Date getIssuedAt(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(ISSUER).build();
DecodedJWT jwt = verifier.verify(token);
return jwt.getExpiresAt();
} catch (Exception e) {
logger.error("获取有效时间失败", e);
return null;
}
}
/**
* 获得token的到期剩余时间 毫秒
*
* @return token中包含的签发时间
*/
public static long getLiveTime(String token) {
try {
Date date = getIssuedAt(token);
if (date != null) {
return (date.getTime() - new Date().getTime());
}
} catch (Exception e) {
logger.error("获取剩余有效时间失败", e);
return 0;
}
return 0;
}
/**
* token是否过期
*
* @return true:过期
*/
public static boolean isTokenExpired(String token) {
Date now = Calendar.getInstance().getTime();
Date issuedAt = getIssuedAt(token);
return issuedAt == null ? true : issuedAt.before(now);
}
/**
* 生成随机盐,长度32位
*
* @return
*/
/*
public static String generateSalt() {
SecureRandomNumberGenerator secureRandom = new SecureRandomNumberGenerator();
String hex = secureRandom.nextBytes(16).toHex();
return hex;
}
*/
/**
* 无需解密直接获得token中的用户名
*
* @return token中包含的用户名
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
e.printStackTrace();
return null;
}
}
}
下面解决第二个问题,如何将创建好的jwt token 返回给客户端?
一般解决方案是,将生成好的jwt token 设置到请求头中,返回给客户端
rep.setHeader("token",token);
第三个问题当客户端获取到jwt token,再次请求系统,那么如何携带token呢?
解决方案是,客户端将token设置到cookie中或请求头,然后向服务端发起请求
第四个问题,服务端时如何校验token的呢?
解决方案是,创建一个过滤器,用于过滤用户的请求,在这个过滤器中,获取用户的token,然后校验该token是否合法,如果合法则运行访问系统,如果不合法。则返回异常信息给用户。
具体过滤器代码如下:
package org.learn.filter;
import org.apache.commons.lang3.StringUtils;
import org.learn.jwt.JwtUtil;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JwtFilter implements Filter {
/**
* 排除链接
*/
public List<String> excludes = new ArrayList<>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
{ //排除的连接(不进行过滤的连接)
String tempExcludes = filterConfig.getInitParameter("excludes");
if (StringUtils.isNotEmpty(tempExcludes)) {
String[] url = tempExcludes.split(",");
for (int i = 0; url != null && i < url.length; i++) {
excludes.add(url[i]);
}
}
}
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
//url过滤
if (handleExcludeURL(request, response)) {
filterChain.doFilter(request, response);
return;
}
//获取header中的token
final String token = request.getHeader("jwtToken");
if (token == null) {
response.getWriter().write("token不存在!");
return;
}
String userName = JwtUtil.getUsername(token);
Map<String, Object> repMap = new HashMap<>();
if (null == userName) {
response.getWriter().write("token异常!");
return;
} else {
repMap = JwtUtil.verify("token", userName);//验证token
if (null != repMap && !repMap.isEmpty()) {
userName = (String)repMap.get("username");
//如果token合法,则放行
request.getSession().setAttribute("userName", userName);
} else {
response.getWriter().write("token不合法!");
return;
}
}
filterChain.doFilter(req, res);
}
@Override
public void destroy() {
}
private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {
if (excludes == null || excludes.isEmpty()) {
return false;
}
String url = request.getServletPath();
for (String pattern : excludes) {
Pattern p = Pattern.compile("^" + pattern);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
}
具体实现,就在doFilter方法中,核心代码
String userName = JwtUtil.getUsername(token); //根据token 获取用户名
repMap = JwtUtil.verify("token", userName);//验证token
这样我们就可以验证token 是否合法了。
具体完整代码可以访问我的gitee获取 完整源码获取