springboot整合JWT
一、JWT介绍
JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。
在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。
1.JWT的格式
JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C,第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
A由JWT头部信息header加密得到
B由JWT用到的身份验证信息json数据加密得到
C由A和B加密得到,是校验部分
2.怎样使用token?
可以放到HTTP请求的请求头中,通常是Authorization字段。
3.流程图
二、java代码实现
springboot经典的四个步骤
1.改pom
2.写yml/properties
3.启动类
4.代码
1.maven依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.配置application.properties
没用这个里面的
server.port=8087
# 要加密的明文
jwt.secret=hand2020
# tocken 过期时间,单位秒
jwt.expire=300
3.启动类
这里选择使用@ServletComponentScan,是因为在Filter类用@component和@configuration会导致
@WebFilter(urlPatterns = “/testToken”, filterName = “jwtFilter”) url失效变成拦截所有
package com.example.bootjwt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@SpringBootApplication
@ServletComponentScan //这里是将filter扫描加载进spring容器
public class BootJwtApplication {
public static void main(String[] args) {
SpringApplication.run(BootJwtApplication.class, args);
}
}
4.实现代码
这里主要是做一个简单的demo验证,有三个类JwtController、CreatToken、JwtFilter。
JwtController:用来接收rest请求。
JwtUtil:用来生成token,解密token,验证token
JwtFilter:用来拦截请求对http请求中携带的token进行验证
JwtController
package com.example.bootjwt.controller;
import com.example.bootjwt.Util.JwtUtil;
import com.example.bootjwt.domain.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class JwtController {
@PostMapping("/get")
public String creatToken2(){
User user = new User();
user.setId("1");
user.setUsername("hand2020");
user.setPassword("123456");
return JwtUtil.createJWT(40000,user);
}
@PostMapping("/test")
public String testToken2(HttpServletRequest request, HttpServletResponse response){
String token= request.getHeader("Authorization");
User user = new User();
user.setId("1");
user.setUsername("hand2020");
user.setPassword("123456");
if (JwtUtil.isVerify(token,user)){
return "success";
}
return "fail";
}
}
JwtUtil
这里我是在配置文件中读需要加密的明文,和过期时间。也可以在controller里处理参数设置。
package com.example.bootjwt.Util;
import com.example.bootjwt.domain.User;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class JwtUtil {
// @Value("${jwt.secret}")
// private static String key;
/**
* 用户登录成功后生成Jwt
* 使用Hs256算法 私匙使用用户密码
*
* @param ttlMillis jwt过期时间
* @param user 登录成功的user对象
* @return
*/
public static String createJWT(long ttlMillis, User user) {
//指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("id", user.getId());
claims.put("username", user.getUsername());
claims.put("password", user.getPassword());
//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
String key = user.getPassword();
//生成签发人
String subject = user.getUsername();
//下面就是在为payload添加各种标准声明和私有声明了
//这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
//设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(UUID.randomUUID().toString())
//iat: jwt的签发时间
.setIssuedAt(now)
//代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
.setSubject(subject)
//设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
//设置过期时间
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* Token的解密
* @param token 加密后的token
* @param user 用户的对象
* @return
*/
public static Claims parseJWT(String token, User user) {
//签名秘钥,和生成的签名的秘钥一模一样
String key = user.getPassword();
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
* 校验token
* 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过
* @param token
* @param user
* @return
*/
public static Boolean isVerify(String token, User user) {
//签名秘钥,和生成的签名的秘钥一模一样
String key = user.getPassword();
//Jwts.parser在执行parseClaimsJws(token)时如果token时间过期会抛出ExpiredJwtException异常
try {
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
if (claims.get("password").equals(user.getPassword())) {
return true;
}
}catch (ExpiredJwtException e){
e.printStackTrace();
}
return false;
}
}
JwtFilter
过滤器是通过实现Filter接口,注意@WebFilter相当于xml配置,但是需要在启动类上注解
@ServletComponentScan,将JwtFilter加入到spring容器中。
在JwtFilter类上注解@component或@configuration会导致@WebFilter失效从而拦截所有请求
目前这个没用到,直接在controller里做了判断,这个是后续业务需求的demo
package com.example.bootjwt;
import com.example.bootjwt.Util.JwtUtil;
import com.example.bootjwt.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(urlPatterns = "/testToken", filterName = "jwtFilter")
public class JwtFilter implements Filter {
@Autowired
private CreatToken creatToken;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setHeader("Access-Control-Allow-Origin", "*");
String token= request.getHeader("Authorization");
User user = new User();
user.setId("1");
user.setUsername("hand2020");
user.setPassword("123456");
boolean flag = JwtUtil.isVerify(token,user);
if (flag){
filterChain.doFilter(servletRequest,servletResponse);
}else {
System.out.println("失败。。。。。。。。");
response.getWriter().write("失败。。。。。。。。");
}
}
@Override
public void destroy() {
}
}
##三、 测试效果
由于没有写前端代码,就用postman模拟请求
1.浏览器发送请求获取token
http://localhost:8087/get
2.将token放入请求头中请求
注意将上次请求获得的token放入请求头内(注意不要过太长时间因为token设置了40秒过期)http://localhost:8087/test
token超时:
总结
这个springboot整合jwt只是一个很简单的demo,并不是真正业务中使用方式。后面我会写一个单点登录的例子,会用到jwt。现在这里做一个入门练习。