JWT由Header(请求头)、Payload(携带的用户信息)、Signature(加密后生成的签名)三部分组成,也就是我们用过JWT所眼熟的header.payload.signature。JWT可以自定义Signing Key,能够有效的防止伪造或篡改token签名。
为什么要接口鉴权?怎样的业务场景下需要使用鉴权功能?
1:客户端登录后可操作内容鉴权:打个比方,评论是很常见的业务功能,那么在评论之前客户端需校验用户是否登录,这个时候我们就可以通过使用jwt产生的token来判断用户的登录状态,登录后才会返回token,客户端请求,服务端校验。
2:登录状态过期校验:当然有了登录过后也得有登录过期,JWT可以很简便的设置token expire时间,服务端配置每次都自动校验token是否过期,如果过期就直接抛出异常,客户端需要重新登录申请token。
3:Restful Api 的身份认证:业务接口必须要使用身份验证才允许访问。
1. 导入jwt的maven依赖
io.jsonwebtoken
jjwt
0.7.0
2. 新建JwtHelper类
生定义生成sign key:
public static SecretKey generalKey(){
byte[] encodedKey = Base64.decodeBase64("francis1q2we3");//自定义
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
创建jwt ,claim为自定义加密参数(可以吧用户id 或者用户昵称加入里面)。
/**
* 创建JWT
*/
public static String createJWT(String name, String userId,
long TTLMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 添加构成JWT的参数
JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
.claim("nickName", name)
.claim("memId", userId)
.setIssuer("francis")//发行者,自定义
.setAudience("client")
.signWith(signatureAlgorithm, generalKey());
// 添加Token过期时间
if (TTLMillis >= 0) {
long expMillis = nowMillis + TTLMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp).setNotBefore(now);
}
// 生成JWT
return builder.compact();
}
解析token:
public static Claims parseJWT(String jsonWebToken) {
try {
Claims claims = Jwts.parser().setSigningKey(generalKey())
.parseClaimsJws(jsonWebToken).getBody();
return claims;
} catch (Exception ex) {
System.out.println(ex);
return null;
}
}
从解析的JWT中获取参与加密的claims
public static Integer getUserId(String jsonWebToken){
jsonWebToken = jsonWebToken.substring(7, jsonWebToken.length());
Claims claims = parseJWT(jsonWebToken);
return Integer.parseInt(claims.get("memId").toString());
}
public static String getNickName(String jsonWebToken){
jsonWebToken = jsonWebToken.substring(7, jsonWebToken.length());
Claims claims = parseJWT(jsonWebToken);
return Integer.parseInt(claims.get("nickName").toString());
}
3. 添加JwtFilter过滤器检验token
package com.francis.api.config.jwt;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.filter.GenericFilterBean;
import com.feilong.core.Validator;
import com.xj.api.util.JsonUtil;
import com.xj.api.util.JwtHelper;
import com.xj.common.base.common.bean.Result;
import com.xj.common.base.common.exception.EnumSvrResult;
import com.xj.common.bussiness.member.entity.QMember;
public class JwtFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if("OPTIONS".equals(request.getMethod())){//预请求直接通行
chain.doFilter(request, response);
return;
}
String auth = request.getHeader("token");
if ((auth != null) && (auth.length() > 7)) {
String headStr = auth.substring(0, 6).toLowerCase();
if (headStr.compareTo("bearer") == 0) { //按照国际惯例,客户端在发送token的时候需要告知来人,所以在token前面加上的“bearer;”,这里下步进行token校验时需要分离出来。
auth = auth.substring(7, auth.length());
System.out.println(auth);
if (JwtHelper.parseJWT(auth) != null) {
chain.doFilter(request, response);
return;
}
}
}
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write(JsonUtil.getJsonFromObject(new Result(EnumSvrResult.ERROR_TOKEN))); //自定义的token无效或不存在的返回异常
return;
}
}
4. JwtConfig 用来定义需过滤那些接口。本文定义过滤后缀为“.auth”的接口。
package com.francis.api.config.jwt;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JwtConfig {
@Bean
public FilterRegistrationBean basicFilterRegistrationBean(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter);
List urlPatterns = new ArrayList<>();
urlPatterns.add("*.auth");
registrationBean.setUrlPatterns(urlPatterns);
return registrationBean;
}
}
5. 获取token,登录成功后调用createJWT方法传入对应参数及失效时间(毫秒),即可生成token返回给客户端。
6. 接口路由定义事例:@GetMapping("/getUserInfo/{id}.auth");所有后缀是.auth的接口都会走filter校验流程。
7. 前端如何传递token? 前端获取到token 后只需要在接口请求header中加入token属性,值为' bearer;+ token '
JWT的使用在此告一段落,总体来说操作并不复杂,生成、解析token的方法也很简单。
友情提示: JWT虽好,可不要太放心哦,相对可能存在token 被盗用的风险,所以尽量减少使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
正常业务逻辑中,我们需要一个用户只能在一个地方登录,也就是说在登录状态未失效的情况下,下一次登录必须踢掉上一次登录。然而JWT并没有实现手动token 过期功能,那么我们在项目中要如何实现这一功能呢?
请听下回分解!>>>紧戳这里,直接下回分解!!!
----我是francis, 谨以此记录自己精彩的程序人生。