Spring Boot 集成JWT实现前后端认证的示例代码
目录
- 前言
- JWT简介
- 为什么要用JWT
- 传统session认证存在那些弊端?
- JWT认证的优势
- JWT的数据结构
- Header
- Payload
- Signature
- Spring Boot集成JWT
- 引入Jwt包
- 编写jwt工具类
- Token认证拦截器
- 配置拦击器
- 登录验证流程
- 示例代码
- 总结
前言
小程序、H5应用的快速发展,使得前后端分离已经成为了趋势,然而系统认证却是系统的重要一部分,本文将讲解JWT如何实现前后端认证。
JWT简介
JWT(全称:Json Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。
为什么要用JWT
传统session认证存在那些弊端?
- 每个用户的登录信息都会保存到服务器的Session中,随着用户的增多,服务器开销会明显增大。
- Session的信息存放在服务器的内存中,对于分布式应用会导致失效,虽然可以将session的信息统一存放在Redis的缓存中,但这样可能增加了复杂性。
- 由于Session认证是基于Cookie实现,而针对于非浏览器端和手机的移动端都不适用。
- 前后端分离系统,由于前后端存在跨域,而Cookie信息无法跨越,所以采用Session认证也是无法继续宁跨域认证。
JWT认证的优势
- 简洁:JWT Token数据量小,传输速度也很快。
- 跨语言: JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,任何web形式都支持。 跨平台:不依赖于cookie和session,无需将session信息存放在服务端,非常适合于分布式应用,应用于扩展。
JWT的数据结构
Header
JWT第一部分是头部分,它是一个描述JWT元数据的Json对象,通常如下所示。
{ "alg": "HS256", "typ": "JWT" }
alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256),typ属性表示令牌的类型,JWT令牌统一写为JWT。
Payload
JWT第二部分是Payload,也是一个Json对象,除了包含需要传递的数据,还有七个默认的字段供选择。 iss:发行人 exp:到期时间 sub:主题 aud:用户 nbf:在此之前不可用 iat:发布时间 jti:JWT ID用于标识该JWT
{ //默认字段 "sub":"主题123", //自定义字段 "name":"java", "isAdmin":"true", "loginTime":"2021-12-05 12:00:03" }
需要注意的是,默认情况下JWT是未加密的,任何人都可以解读其内容,因此如果一些敏感信息不要存放在此,以防信息泄露。JSON对象也使用Base64 URL算法转换为字符串保存。
Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。
Spring Boot集成JWT
引入Jwt包
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
编写jwt工具类
public class JwtUtil { //创建jwt public static String createJWT(String subject, String issue, Object claim, long ttlMillis) { //当前时间 long nowMillis = System.currentTimeMillis(); //过期时间 long expireMillis = nowMillis + ttlMillis; String result = Jwts.builder() .setSubject(subject) //设置主题 .setIssuer(issue) //发行者 .setId(issue)//jwtID .setExpiration(new Date(expireMillis)) //设置过期日期 .claim("user", claim)//主题,可以包含用户信息 .signWith(getSignatureAlgorithm(), getSignedKey())//加密算法 .compressWith(CompressionCodecs.DEFLATE).compact();//对载荷进行压缩 return result; } // 解析jwt public static Jws<Claims> pareseJWT(String jwt) { Jws<Claims> claims; try { claims = Jwts.parser().setSigningKey(getSignedKey()) .parseClaimsJws(jwt); } catch (Exception ex) { claims = null; } return claims; } //获取主题信息 public static Claims getClaims(String jwt) { Claims claims; try { claims = Jwts.parser().setSigningKey(getSignedKey()) .parseClaimsJws(jwt).getBody(); } catch (Exception ex) { claims = null; } return claims; } } /** * 获取密钥 * * @return Key */ private static Key getSignedKey() { byte[] apiKeySecretBytes = DatatypeConverter .parseBase64Binary(getAuthKey()); Key signingKey = new SecretKeySpec(apiKeySecretBytes, getSignatureAlgorithm().getJcaName()); return signingKey; } private static SignatureAlgorithm getSignatureAlgorithm() { return SignatureAlgorithm.HS256; } //获取密钥,可以动态配置 public static String getAuthKey() { String auth = "123@#1234"; }
Token认证拦截器
Component public class TokenInterceptor extends HandlerInterceptorAdapter { public static Log logger = LogManager.getLogger(TokenInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri = request.getRequestURI(); logger.info("start TokenInterceptor preHandle.." + uri); //需要过滤特殊请求 if (SystemUtil.isFree(uri) || SystemUtil.isProtected(uri)) { return true; } String metohd=request.getMethod().toString(); logger.info("TokenInterceptor request method:"+metohd); //options 方法需要过滤 if("OPTIONS".equals(metohd)) { return true; } //是否开启token认证 boolean flag = SystemUtil.getVerifyToken(); ResponseResult result = new ResponseResult(); //从请求的head信息中获取token String token = request.getHeader("X-Token"); if (flag) { if(StringUtils.isEmpty(token)) { token=request.getParameter("X-Token"); } // token不存在 if (StringUtils.isEmpty(token)) { result.setCode(ResultCode.NEED_AUTH.getCode()); result.setMsg(ResultCode.NEED_AUTH.getMsg()); WebUtil.writeJson(result, response); return false; } else { Claims claims = JwtUtil.getClaims(token); String subject = ""; if (claims != null) { subject = claims.getSubject(); // 验证主题 if (StringUtils.isEmpty(subject)) { result.setCode(ResultCode.INVALID_TOKEN.getCode()); result.setMsg(ResultCode.INVALID_TOKEN.getMsg()); WebUtil.writeJson(result, response); return false; } } else { result.setCode(ResultCode.INVALID_TOKEN.getCode()); result.setMsg(ResultCode.INVALID_TOKEN.getMsg()); WebUtil.writeJson(result, response); return false; } } } return true; } }
配置拦击器
@Configuration public class WebConfig implements WebMvcConfigurer { @Resource private TokenInterceptor tokenInterceptor; public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); } }
登录验证流程
示例代码
@RequestMapping("login") public Result login(HttpServletResponse response) { Map<String, Object> map = new HashMap<>(); // Result result = loginAuth(user); int code = result.getCode(); //登录认证成功 if (code ==ResultCode.SUCCESS) { //默认为7天 Long ttlMillis = 7*1000 * 60 * 60 * 24; //过期时间 long expreTime = System.currentTimeMillis() + ttlMillis; String tokenKey = UUID.randomUUID().toString(); String tokenId = JwtUtil.createJWT(user.getUserId(), tokenKey, user.getPassword(), expreTime); map.put("expreTime", expreTime); map.put("tokenId", tokenId); } else { logger.error("login error:" +FastJsonUtil.toJSONString(result)); } return result; }
总结
时代在进步,技术也在不断更新,以前采用Session+Redis实现单点登录,现在可以替换为jwt+Redis实现,我们只有不断的更新自己的技术栈,才能避免被无情的淘汰,关于使用注解方式实现和token刷新,将在后续文章中进行讲解。
到此这篇关于Spring Boot 集成JWT实现前后端认证的示例代码的文章就介绍到这了,更多相关SpringBoot JWT前后端认证内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!