SpringBoot 2.1.4集成JWT实现token验证

2019/5/17 14:27:22 来源:wRitchie 浏览:

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_nz6XtcCyi1hgvpn8bf95gwg。

第一步:pom.xml添加依赖,引入jar包:

版本声明

<properties>
<!--jjwt -->
<jjwt.version>0.9.1</jjwt.version>
</properties>

版本依赖:

<dependencies>

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>${jjwt.version}</version>

</dependency>

</dependencies>

第二步:实现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<String, Object> map = new 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<String, Object> body = Jwts.parser()

.setSigningKey(SECRET)

.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))

.getBody();

//遍历自定的相关数据,可以删除

for (Map.Entry<String, Object> entry : body.entrySet()) {

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<String, String> claims;

public CustomHttpServletRequest(HttpServletRequest request, Map<String, ?> claims) {

super(request);

this.claims = new HashMap<>();

claims.forEach((k, v) -> this.claims.put(k, String.valueOf(v)));

}

@Override

public Enumeration<String> getHeaders(String name) {

if (claims != null && claims.containsKey(name)) {

return Collections.enumeration(Arrays.asList(claims.get(name)));

}

return super.getHeaders(name);

}

public Map<String, String> getClaims() {

return claims;

}

}

static class TokenValidationException extends RuntimeException {

public TokenValidationException(String msg) {

super(msg);

}

}

}

第五步:在控制类中编写接口,如LoginController.java,对于需要令牌token验证的,可以相应的请求路径中加JwtAuthenticationFilter 类中定义的匹配规则标识符,如“api“,例如在LoginController类中,登录方法public Result<Map<String, Object>> login(@RequestBody User user) ,注解路径映射@PostMapping("/login")中,不含“api”,故该方法无需令牌token验证;根据用户ID获取用户信息方法public Result<Map<String, Object>> getUserInfo(@RequestHeader(value = JwtUtil.USER_NAME) String userId),注解路径映射@GetMapping("/api/getUserInfo")中含有“api”,故该方法令牌token验证;如要总个控制类中的方法都需令牌token验证,则在类的注解路径映射中加“api”,例如:注解路径映射@RequestMapping("loginController")改为注解路径映射@RequestMapping("/api/loginController"),具体示例代码如下:

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<Map<String, Object>> sign(String loginName) {

logger.info("#######LoginController#######");

Map<String, Object> userMap = new HashMap<String, Object>();

userMap.put("loginName", loginName);

List<Map> userList = userService.selectByPager(userMap);

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<Map<String, Object>> login(@RequestBody User user) {

logger.info("####### login #######" + user.getLoginName() + " password:" + user.getPassword());

String jwt="";

String pwdTmp = MD5Util.encode(user.getPassword());

//logger.info("####登录名:"+loginName+"\t密码:" + password);

String sKey = MD5Util.md5(SystemConstant.SYSTEM_SKEY).substring(0, 16);

String pwdEncrypt = AESUtil.encrypt(pwdTmp, sKey).substring(0, 16);

logger.info("密码加密:" + pwdEncrypt);

Map<String, Object> userMap = new HashMap<String, Object>();

userMap.put("loginName", user.getLoginName());

List<Map> userList = userService.selectByPager(userMap);

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<String, Object> param=new HashMap<String, Object>();

param.put("userId", userMap.get("userId"));

List<Menu> menuList=menuService.selectBySelectiveByUserId(param);

userMap.put("menuList",menuList);

return Result.success(userMap);

} else {

logger.info("密码错误。");

userMap.put("authorized", new ResponseEntity(HttpStatus.UNAUTHORIZED));

userMap.put("token", jwt);

return Result.failure("密码错误,登录失败。", userMap);

}

} else {

logger.info("用户不存在,请先注册。");

userMap.put("authorized", new ResponseEntity(HttpStatus.UNAUTHORIZED));

userMap.put("token", jwt);

return Result.failure("用户不存在,请先注册,登录失败。", userMap);

}

}

@GetMapping("/api/getUserInfo")

@ApiOperation(value = "获取用户信息", notes = "根token获取用户的信息")

public Result<Map<String, Object>> getUserInfo(@RequestHeader(value = JwtUtil.USER_NAME) String userId) {

logger.info("#######LoginController getUserInfo#######");

Map<String, Object> userMap = new HashMap<String, Object>();

User user = userService.selectByPrimaryKey(Integer.valueOf(userId));

userMap.put("user", user);

return Result.success("授权成功。", userMap);

}

@GetMapping("/getUserByToken")

@ApiOperation(value = "查询用户信息", notes = "根据token获取用户的信息")

public Result<Map<String, Object>> getUserByToken(String token) {

logger.info("#######LoginController getUserByToken#######");

Map<String, Object> userMap = new HashMap<String, Object>();

String userId = "";

try {

if (token != null) {

Map<String, Object> body = Jwts.parser()

.setSigningKey(JwtUtil.SECRET)

.parseClaimsJws(token.replace(JwtUtil.TOKEN_PREFIX, ""))

.getBody();

for (Map.Entry<String, Object> entry : body.entrySet()) {

logger.info("****getUserByToken JWT paser Key = " + entry.getKey() + ", Value = " + entry.getValue());

if ("userId".equals(entry.getKey())) {

userId = (String) entry.getValue();

break;

}

}

} else {

logger.info("The token is null!");

return Result.failure("The token is null!",userMap);

}

if (!StringUtil.isEmpty(userId)) {

User user = userService.selectByPrimaryKey(Integer.valueOf(userId));

userMap.put("user", user);

return Result.success("根据token获取用户的信息成功。", userMap);

}

return Result.failure();

} catch (Exception e) {

return Result.exception("根据token获取用户的信息异常:",userMap);

}

}

}

第六步:测试API,具体结果如下图所示:

1、登录,请求服务授权,返回令牌token,如下图所示

2、根据返回的令牌token,请求根据用户ID获取用户信息API,正确的token请求API返回如下图:

3、修改返回的令牌token,使用错误的令牌token请求根据用户ID获取用户信息API,错误的token请求API返回如下图:

正确错误令牌token请求时,IDEA控制台日志信息如下图所示:

至止,SpringBoot集成JWT实验token验证完毕,根据具体的实际应用项目,适当修改即可以使用。

看看网友怎么说

所谓伊人16:转发了

鹏126705954:转发了

黄杨扁担呀么软溜溜:不如放个github链接

黄伊凯:转发了

IT之家刺客:昨天刚升级的2.1.4,今天看文档发现2.1.5才是最新current版[我想静静][我想静静][我想静静]

春之礼赞:转发了

划破天空209:转发了

林李大战2:转发了

Java比海盗:转发学习了

罗家新户主:转发了

文章来源网络,版权归属原作者,未注明作者均因传阅太多无从查证。本站为公益性非盈利网站,在本网转载其他媒体稿件是为传播更多的信息,此类稿件不代表本网观点。如果本网转载的稿件涉及您的版权、名益权等问题,请尽快与我们联系,我们将第一时间处理!
  • 文/图李晓聆第二次去萝卜寨,是在樱桃成熟的时节。车沿着盘山公路慢慢往上爬,越到高处樱桃树越多,挂满果实的樱桃树吸引了我们的视线,使我早已忘掉了对陡峭狭窄的公路的恐惧。2014年去萝卜寨,是为了拍节目,行色匆匆,对云朵上的羌寨没有太多的感受。这次我们真的有了“天空之城”的感觉。我们在汶川就吃过了晚饭。...

  • 2015年,新南威尔士大学量子大牛AndrewDzurak教授曾经带领他的团队实现一项量子计算机的里程碑成果——用半导体材料硅制造出量子逻辑门(quantumlogicgate),首次使两个量子比特(quantumbits)或者“量子位”(qubits)间信息计算成为可能。作为打造量子计算机的基本元...

  • 点击蓝字“中国电科”,关注CETC品牌微刊近日,以中国电科11所自主开发的多谱段集成红外探测器为主要载荷的高分五号卫星正式投入使用,标志着高分专项打造的高空间分辨率、高时间分辨率、高光谱分辨率的天基对地观测能力中最有应用特色的高光谱能力形成。用全谱段高光谱卫星对大气和陆地进行综合观测,在国际上尚属首...

  • 【民生调查局】编者按:这里是民生调查局,见人所未见,调查民生之变。关注你想关注的、你没关注的,调查你想看的、未看到的。中新网客户端北京5月14日电题:荔枝贵到吃不起?“水果自由”你实现了吗?作者谢艺观“车厘子自由”后,近日,“荔枝自由”又喜提热搜了,实际上,今年的荔枝真的这么贵吗?“水果自由”真的难...

  • 相信很多人都在使用360或者其他的一些电脑管理软件,它们的好处就是把一些大家常用的软件集成在一起了,在大家想使用或者想找的时候都会很便利。但是这一类的软件作为商业化公司旗下的产物,商业化气息肯定是更严重。虽然使用是免费的,但是有一些让用户反感的内容。比如商业广告,捆绑安装,浏览器主页强制锁定等等一些...

  • 今天,腾讯公布了2019年第一季度业绩。财报显示,2019年Q1,腾讯总收入为854.65亿元,同比增长16%;非通用会计准则下,净利润为209.30亿元,同比增长14%。腾讯Q1净利润高于16家投行分析师预估的203.88亿元中位数,超过市场预期。本季,腾讯在财报中单列“金融科技及企业服务收入”,...

  • 七场较量过后,开拓者最终逆转掘金,挺进了西部决赛。这对开拓者球迷来说绝对算得上久违,他们终于可以在摩达中心观赏到属于自己的西部最终较量了。当然,这对利拉德来说,意义更是非凡。他当初放出的话,曾被无数人嘲笑,如今他终于可以率领球队狠狠打脸。这件事发生在2017年的夏窗,那时保罗转会到火箭,同时安东尼成...

  • 中国科学技术大学乔振华课题组与南方科技大学张立源课题组等合作,经过5年多的努力,首次在毫米级的碲化锆材料上观测到三维量子霍尔效应,研究成果5月9日发表在国际权威期刊《自然》上,引发学术圈的极大关注。美国国家科学院院士文小刚高度评价这一成果:“这一新的实验发现,给了我们一个新的材料体系,其中也能产生拓...

  • 昨日收盘看,各指数的表现还不错。单就上证的波动来看,股指两次摸到了2940以上,充分印证了阿牛在5月6日暴跌当天提出该点位“将成为随后反弹的压力点,但短线攻克应该不难”的判断。早上市场强得不行,你别看指数弱势震荡,但是大家做多的意愿都非常强,不过美中不足的一点就是,这批高位强势股今天出现了分歧,像吴...

  • 相信大部分玩家都认为山崎龙二是来自《拳皇》系列中的人物吧!在《拳皇97》中,作为97特别队的玛丽、比利和山崎龙二,其实都是来自饿狼传说中的经典人物,而山崎龙二的剧情到了这里才刚刚开始。在《拳皇97》中剧情线中人气最高的就是八杰集,而麦卓、暴风高尼茨、嘉迪路和薇思已经死了,仅存的只有四位,而令玩家意外...