JWT,全称是Json Web Token, 是一种JSON风格的轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权。它是基于 RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据是使用数字签名的,所以是可信任的和安全的。JWT可以使用HMAC算法对secret进行加密或者使用RSA的公钥私钥对来进行签名。
来看一下 JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
token 分成了三部分,头部(header),荷载(Payload) 和 签名(Signature),每部分用 . 分隔,其中头部和荷载使用了base64编码,分别解码之后得到两个JSON串:
第一部分-头部:
{
"alg": "HS256",
"typ": "JWT"
}
alg字段为加密算法,这是告诉我们 HMAC 采用 HS512 算法对 JWT 进行的签名。
第二部分-荷载:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
荷载的字段及含义:
这段告诉我们这个Token中含有的数据声明(Claim),这个例子里面有三个声明:sub, name 和 iat。在我们这个例子中,分别代表着所面向的用户、用户名、创建时间,当然你可以把任意数据声明在这里。
第三部分-签名:
第三部分签名则不能使用base64解码出来,该部分用于验证头部和荷载数据的完整性。
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
同时在配置文件properties中加入:
# 密钥
jwt.secret=mySecret
jwt.header=Authorization
# token 过期时间 2个小时
jwt.expiration=7200000
/**
* @program sweet-dream
* @description: JWT工具类
* @author: zhangchao
* @date: 2020/03/21 00:29
* @since: 1.0.0
*/
@Slf4j
@Component
public class JwtTokenUtil implements Serializable {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.header}")
private String header;
@Value("${jwt.expiration}")
private long expiration;
/**
* 获取token中的信息
* @param token 生成的token
* @return 信息
*/
public String getInfoFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
/**
* 获取token的生成时间
* @param token 生成的token
* @return token的生成时间
*/
public Date getIssuedAtDateFromToken(String token) {
return getClaimFromToken(token, Claims::getIssuedAt);
}
/**
* 获取token的过期时间
* @param token 生成的token
* @return token的过期时间
*/
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
/**
* 判断token是否过期
* @param token 生成的token
* @return true:过期,false:失效
*/
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(Date.from(Instant.now()));
}
public <T> T getClaimFromToken(String token, Function<Claims,T> claimsResolver){
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
return (lastPasswordReset != null && created.before(lastPasswordReset));
}
private Boolean ignoreTokenExpiration(String token) {
// here you specify tokens, for that the expiration is ignored
return false;
}
/**
* 生成令牌
* @param userDetails
* @return
*/
public String makeToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
/**
* 真正进行创建token的方法
* @param claims
* @param subject
* @return
*/
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = Date.from(Instant.now());
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims) /* 自定义属性 */
.setSubject(subject) /* 该JWT所面向的用户 */
.setIssuedAt(createdDate) /* 设置发放的时间,类型为: Date*/
.setExpiration(expirationDate) /* 设置过期时间 类型为:Date */
.signWith(SignatureAlgorithm.HS512, secret) /* jwt签名算法和密钥 */
.compact(); /* 返回一个URL安全JWT字符串 */
}
/**
* 刷新token
* @param token
* @return
*/
public String refreshToken(String token) {
final Date createdDate = Date.from(Instant.now());
final Date expirationDate = calculateExpirationDate(createdDate);
final Claims claims = getAllClaimsFromToken(token);
claims.setIssuedAt(createdDate);
claims.setExpiration(expirationDate);
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
SecurityUser user = (SecurityUser) userDetails;
final Date created = getIssuedAtDateFromToken(token);
/* final Date expiration = getExpirationDateFromToken(token);
如果token存在,且token创建日期 > 最后修改密码的日期 则代表token有效*/
return (!isTokenExpired(token)
/*&& !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())*/
);
}
/**
* 生成过期时间
* @param createdDate 当前时间
* @return 返回到期时间
*/
private Date calculateExpirationDate(Date createdDate) {
return Date.from(Instant.ofEpochMilli(createdDate.toInstant().toEpochMilli()+expiration));
}
}
/**
* @author ZHANGCHAO
* @date 2020/3/13 9:35
* @since 1.0.0
*/
@Slf4j
@Component
public class LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
log.info("登录成功!");
/* 默认:会帮我们跳转到上一次请求的页面上 */
//super.onAuthenticationSuccess(request, response, authentication);
//生成token
String token = jwtTokenUtil.makeToken((UserDetails) authentication.getPrincipal());
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
PrintWriter writer = response.getWriter();
writer.write("{\"status\":\"ok\",\"msg\":\"登录成功\",\"token\":\""+token+"\"}");
writer.flush();
writer.close();
}
}
/**
* @program sweet-dream
* @description: JWT过滤器
* @author: zhangchao
* @date: 2020/03/21 00:48
* @since: 1.0.0
*/
@Slf4j
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Value("${jwt.header}")
private String header;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String header = request.getHeader(this.header);
String authToken = null;
String username = null;
if (null != header && header.startsWith("Bearer")){
authToken = header.replace("Bearer ","");
log.info("获取token: {}",authToken);
username = jwtTokenUtil.getInfoFromToken(authToken);
}
if (null != username && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.customUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); /* 增加额外数据 */
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request,response);
}
}
// 禁用CSRF防护
http.csrf().disable(); http.addFilterBefore(jwtAuthorizationTokenFilter,UsernamePasswordAuthenticationFilter.class);
输入用户名密码和验证码,登录成功后可以看到已经返回token了,以后每次请求都要在header里带上token。比如访问/test接口:
以上!