wRitchie 2019-05-16 15:11:24
SpringBoot 2.1.4集成JWT实现token验证
编者: wRitchie(吴理琪) 来源:http://www.bj9420.com
什么是JWT:Json web token (JWT) 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA或ECDSA的公私秘钥对进行签名。
JWT如何获取访问令牌(token)并用于访问资源(API)流程:1、应用端向权限服务器请求授权;2、权限服务器授权成功向应用端返回一个访问令牌(token);3、应用端使用访问令牌(token)访问受保护的资源(如API)。
JWT是由三段信息构成,将这三段信息文本用“.“连接一起就构成了JWT字符串,Header.Payload.Signature,例如:eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE1NTgwNjI2OTYsInVzZXJJZCI6IjEifQ.XiI0xjX0izVeJRmhbXN1w1fXKdHB0wsc9teFKq84pclpJt6yS2k0BVXAklHrke_nz6XtcCyi1hg8bf95gwg。
第一步:pom.xml添加依赖,引入jar包:
版本声明
0.9.1
版本依赖:
第二步:实现JWT的token验证共3个Java类。首先注册一个检验JWT的过滤器jwtFilter, 通过jwtFilter过滤器实现对每个Rest API请求都验证JWT的功能。 其中JwtAuthenticationFilter继承了OncePerRequestFilter,任何请求都会先经过jwtFilter过滤器, 然后选择让合法JWT请求通过jwtFilter。 具体在SpringBoot的Application启动类中增加@Bean注解,代码如下:
@Bean
public FilterRegistrationBean jwtFilter() {
logger.info("JWT Filter 运行中...");
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
JwtAuthenticationFilter filter = new JwtAuthenticationFilter();
registrationBean.setFilter(filter);
return registrationBean;
}
第三步:再看JWT权限过滤器JwtAuthenticationFilter.java,JwtAuthenticationFilter类继承了OncePerRequestFilter抽象类, 确保任何用户请求资源都会运行doFilterInternal方法。此处将从HTTP Header里面截取JWT, 并且验证JWT的签名和过期时间,若有问题,会返回HTTP 401错误。
package com.bj9420.jwt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author: wRitchie
* @Description: JWT权限过滤器
* @Param:
* @return:
* @Date: 2019/1/14 14:32
*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
private static final PathMatcher pathMatcher = new AntPathMatcher();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("JWT权限过滤器 JwtAuthenticationFilter开始...");
try {
String servletPath = request.getServletPath();
if(isProtectedUrl(request)) {
log.info("私密API请求:"+servletPath);
request = JwtUtil.validateTokenAndAddUserIdToHeader(request);
}else{
log.info("开放API请求:"+servletPath);
}
} catch (Exception e) {
log.info("JWT权限过滤器异常:"+e);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
filterChain.doFilter(request, response);
}
/**
* @Author: wRitchie
* @Description: 是否需要token验证 路径中url含api,则返回true,不含则返回false
* @Param: [request]
* @return: boolean
* @Date: 2019/1/14 14:34
*/
private boolean isProtectedUrl(HttpServletRequest request) {
/** 对路径中url含有api的请求的返回true */
boolean matchFlag = pathMatcher.match("/**/api/**", request.getServletPath());
return matchFlag;
}
}
第四步: JwtUtil.java工具类,主要是生成令牌方法和验证令牌是否有效,具体如下:
package com.bj9420.jwt;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;
/**
* @Author: wRitchie
* @Description: JwtUtil工具类
* @Param:
* @return:
* @Date: 2019/1/14 15:14
*/
public class JwtUtil {
private static final Logger log = LoggerFactory.getLogger(JwtUtil.class);
/**
* 失效时间: 1 天 =24*3600*1000 ms
*/
public static final long EXPIRATION_TIME = 1 * 24 * 3600 * 1000;
/**
* 私钥
*/
public static final String SECRET = "http://www.bj9420.com/jwt";
/**
* token 前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* Authorization header
*/
public static final String HEADER_STRING = "Authorization";
/**
* 保存的数据字段名称
*/
public static final String USER_NAME = "userId";
public static String generateToken(String userId) {
HashMap
//可以将自定义相关的数据放入Map中
map.put(USER_NAME, userId);
String jwt = Jwts.builder()
.setClaims(map)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
//jwt前面一般都会加Bearer
return TOKEN_PREFIX + jwt;
}
public static HttpServletRequest validateTokenAndAddUserIdToHeader(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
log.info("请求token:" + token);
if (token != null) {
// 解析令牌token.
try {
Map
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody();
//遍历自定的相关数据,可以删除
for (Map.Entry
log.info("****JWT paser Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
return new CustomHttpServletRequest(request, body);
} catch (Exception e) {
log.info(e.getMessage());
throw new TokenValidationException(e.getMessage());
}
} else {
throw new TokenValidationException("The token is invalid!");
}
}
public static class CustomHttpServletRequest extends HttpServletRequestWrapper {
private Map
public CustomHttpServletRequest(HttpServletRequest request, Map
super(request);
this.claims = new HashMap<>();
claims.forEach((k, v) -> this.claims.put(k, String.valueOf(v)));
}
@Override
public Enumeration
if (claims != null && claims.containsKey(name)) {
return Collections.enumeration(Arrays.asList(claims.get(name)));
}
return super.getHeaders(name);
}
public Map
return claims;
}
}
static class TokenValidationException extends RuntimeException {
public TokenValidationException(String msg) {
super(msg);
}
}
}
第五步:在控制类中编写接口,如LoginController.java,对于需要令牌token验证的,可以相应的请求路径中加JwtAuthenticationFilter 类中定义的匹配规则标识符,如“api“,例如在LoginController类中,登录方法public Result
package com.bj9420.controller.login;
import com.bj9420.controller.common.BaseController;
import com.bj9420.framework.SystemConstant;
import com.bj9420.framework.util.AESUtil;
import com.bj9420.framework.util.MD5Util;
import com.bj9420.framework.util.StringUtil;
import com.bj9420.jwt.JwtUtil;
import com.bj9420.model.Menu;
import com.bj9420.model.Result;
import com.bj9420.model.User;
import com.bj9420.service.menu.IMenuService;
import com.bj9420.service.user.IUserService;
import io.jsonwebtoken.Jwts;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Title: LoginController.java
* @Description: 登录控制类
* @author: wRitchie
* @date: 2018/12/28 15:39
* @version: V1.0
* @Copyright (c): 2018 http://bj9420.com All rights reserved.
*/
@RestController
@RequestMapping("loginController")
@Api(value = "登录控制类", tags = "登录控制类")
public class LoginController extends BaseController {
@Autowired
private IUserService userService;
@Autowired
private IMenuService menuService;
@GetMapping("sign")
@ApiOperation(value = "登录测试", notes = "无权限验证的登录测试")
public Result
logger.info("#######LoginController#######");
Map
userMap.put("loginName", loginName);
List
if (userList.size() > 0) {
userMap.put("password", "");
User user = userService.selectByPrimaryKey(1);
userMap.put("user", user);
return Result.success(userMap);
} else {
return Result.failure("用户不存在,请先注册。", null);
}
}
@PostMapping("/login")
@ApiOperation(value = "登录接口", notes = "公开权限,用户登录后返回token")
public Result
logger.info("####### login #######" + user.getLoginName() + " password:" + user.getPassword());
String jwt="";
String pwdTmp = MD5Util.encode(user.getPassword());
//logger.info("####登录名:"+loginName+" 密码:" + password);
String sKey = MD5Util.md5(SystemConstant.SYSTEM_SKEY).substring(0, 16);
String pwdEncrypt = AESUtil.encrypt(pwdTmp, sKey).substring(0, 16);
logger.info("密码加密:" + pwdEncrypt);
Map
userMap.put("loginName", user.getLoginName());
List
if (userList.size() > 0) {
userMap = userList.get(0);
String pwdDb = userMap.get("password") + "";
if (pwdDb.equals(pwdEncrypt)) {
userMap.put("password", "");
jwt = JwtUtil.generateToken(userMap.get("userId") + "");
userMap.put("token", jwt);
Map
param.put("userId", userMap.get("userId"));
List