目录
5.过滤器校验jwt的安全(有无篡改,是否失效,注意,并不能保证信息不泄露,这点应提前了解)
前言:
做一个老项目改造,将之前的mvc改为前后端分离的版本
想到之前写小程序时用的jwt就顺便也给session干掉换成jwt了
session和jwt的区别大家自己另行百度,我这就是做完整合下步骤,不会的可以参考下
1.pom依赖
<!--JSON Web Token Support,JWT工具-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2.工具类
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.impl.DefaultClaims;
import org.apache.commons.lang3.StringUtils;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Date;
import java.util.Random;
public class JwtUtils {
//默认过期时间,单位秒
public static final long DEFAULT_EXPIRE_TIME = 2 * 60 * 60;
private static final String DEFAULT_SECRET="4dc25eeb75ba9ca1e4c504af3cb6259f" ;
/**
* 生成jwt字符串
* @param expiration 有效时长,单位秒
* @param secret 秘钥(盐)
* @return
* @throws UnsupportedEncodingException
*/
public static String generateJwt(String subjectId, long expiration, String secret) throws Exception {
if(StringUtils.isBlank(secret)|| secret.length()< 8) {
secret = DEFAULT_SECRET;
}
byte[] secretBytes = secret.getBytes("UTF-8");
DefaultClaims claims = new DefaultClaims();claims.setSubject(subjectId);
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate(expiration))
.signWith(SignatureAlgorithm.HS256,secretBytes).compact();
}
/**
* 生成jwt字符串
* @param claims
* @param expiration 有效时长,单位秒
* @param secret 秘钥(盐)
* @return
* @throws UnsupportedEncodingException
*/
public static String generateJwt(Claims claims, long expiration, String secret) throws Exception {
if(StringUtils.isBlank(secret)|| secret.length()< 8) {
secret = DEFAULT_SECRET;
}
byte[] secretBytes = secret.getBytes("UTF-8");
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate(expiration))
.signWith(SignatureAlgorithm.HS256,secretBytes).compact();
}
public static String getSubject(String jwt,String secret) throws Exception {
return JwtUtils.getPayload(jwt,secret).getSubject();
}
public static Date getExpiration(String jwt,String secret) throws Exception {
return JwtUtils.getPayload(jwt, secret).getExpiration();
}
/**
* 获取jwt的payload
* @param jwt
* @param secret 秘钥(盐)
* @return
* @throws UnsupportedEncodingException
*/
public static Claims getPayload(String jwt,String secret) throws ExpiredJwtException,
MalformedJwtException, SignatureException, IllegalArgumentException, UnsupportedEncodingException {
if(StringUtils.isBlank(secret)|| secret.length()< 8) {
secret = DEFAULT_SECRET;
}
byte[] secretBytes = secret.getBytes("UTF-8");
return (Claims)Jwts.parser().setSigningKey(secretBytes).parse(jwt).getBody();
}
public static Claims getPayloadWithoutCheck(String jwt) throws JsonProcessingException, UnsupportedEncodingException {
String[] split = jwt.split("\\.");
String body = new String(Base64.getDecoder().decode(split[1]), "UTF-8");
return JSONObject.parseObject(body,DefaultClaims.class);
}
/**
* 验证jwt 时效
* @param claims
* @return
*/
public static boolean validateJwtExpire(Claims claims){
Date date = claims.getExpiration();
return new Date(System.currentTimeMillis()).before(date);
}
/**
* 生成token的过期时间
*/
private static Date generateExpirationDate(long expiration) {
if(expiration<=0){
expiration = DEFAULT_EXPIRE_TIME;
}
return new Date(System.currentTimeMillis() + expiration * 1000);
}
}
3.登录生成jwt返回给前端,以后请求头中放入jwt
@POST
@Path("/login")
public ComResult login(@ModelAttribute User user) {
try {
//调用的是中台提供的登录判断接口,登录成功返回token,失败返回code不同
RestResponse<LoginRespDto> login = memberCenterApi.login(user.getUsername(), user.getPassword());
if (RestResponse.SUCC_CODE.equals(login.getResultCode())) {
//加入jwt携带信息
Claims claimsMap = new DefaultClaims();
claimsMap.setSubject(login.getData().getToken());
claimsMap.put("userId", user.getUsername());
String jwt = "";
try {
jwt = JwtUtils.generateJwt(claimsMap, 1200, null);
} catch (Exception e) {
log.error(e.getMessage());
}
Map<String, Object> responseMap = new HashMap<>();
responseMap.put("userId", user.getUsername());
responseMap.put("auth", jwt);
return ComResult.success("登录成功", responseMap);
} else {
return ComResult.failed(login.getResultMsg());
}
} catch (Exception e) {
log.error("调用会员中台登录接口失败:{}", e.getMessage());
log.error("调用会员中台登录接口失败param:{}", JSON.toJSONString(user));
return ComResult.failed("调用会员中台登录接口失败");
}
}
4.其余接口使用jwt中的payload信息
@GET
@Path("/logout")
public ComResult logout(@HeaderParam("auth") String auth) {
String token = null;
try {
token = JwtUtils.getPayloadWithoutCheck(auth).getSubject();
} catch (Exception e) {
log.error("jwt解析异常:{}", e.getMessage());
return ComResult.failed("jwt解析异常");
}
try {
RestResponse<Void> logout = memberCenterApi.logout(token);
if (RestResponse.SUCC_CODE.equals(logout.getResultCode())) {
return ComResult.success(logout);
} else {
return ComResult.failed(logout.getResultMsg());
}
} catch (Exception e) {
log.error("调用会员中台注销接口失败:{}", e.getMessage());
log.error("调用会员中台注销接口失败param:{}", JSON.toJSONString(token));
return ComResult.failed("调用会员中台注销接口失败");
}
}
5.过滤器校验jwt的安全(有无篡改,是否失效,注意,并不能保证信息不泄露,这点应提前了解)
普通springMVC过滤器创建步骤:
继承Filter类,实现doFilter方法-->在web.xml文件中配置mapping
import com.alibaba.fastjson.JSON;
import com.lppz.product.web.common.ComResult;
import com.lppz.product.web.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
public class AuthFilter implements Filter {
/**
* 授权不通过
*/
public static final String UNAUTHORIZED = "999";
/**
* 调用会员中台接口重试次数
*/
public static final int RETRYTIME = 3;
private String jwtSecret;
/**
* 过滤器开关
*/
private final boolean enabled = false;
@Override
public void init(FilterConfig filterConfig) {
jwtSecret = filterConfig.getInitParameter("jwt_secret");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//未开启,不需过滤,直接放行
if (!enabled) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//排除的链接
String excludeStr = "/webresources/ui/member/login";
String[] excludes = excludeStr.split(",");
//排除的链接,不需过滤 直接放行
if (handleExcludeUrl(req, excludes)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/json");
//获取jwt token 命名为auth,为了跟中台的token做区分
String auth = req.getHeader("auth");
log.info("获取到客户端携带的auth:{}", auth);
if (StringUtils.isBlank(auth)) {
log.info("获取到客户端携带的auth为空");
resp.getWriter().println(JSON.toJSONString(ComResult.failed(UNAUTHORIZED,"获取到客户端携带的token为空",null)));
return;
}
//校验jwt token 是否真实,是否超时
Claims payload = null;
try {
payload = JwtUtils.getPayload(auth,jwtSecret);
} catch (ExpiredJwtException e) {
log.warn("jwt auth已过期:[{}]", auth);
resp.getWriter().println(JSON.toJSONString(ComResult.failed(UNAUTHORIZED,"token已过期",null)));
return;
} catch (Exception e) {
log.warn("jwt auth校验不通过:[{}]", auth);
resp.getWriter().println(JSON.toJSONString(ComResult.failed(UNAUTHORIZED,"token校验不通过",null)));
return;
}
//获取token信息
Object userId = payload.get("userId");
//校验通过,则解析jwt token 获取信息 设置到request中,放行
servletRequest.setAttribute("userId", userId);
log.debug("当前登录人是{}", userId);
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
private boolean handleExcludeUrl(HttpServletRequest req, String[] excludes) {
if (excludes == null || excludes.length < 1) {
return false;
}
String url = req.getRequestURI();
for (String pattern : excludes) {
if (pattern.equals(url)) {
return true;
}
}
return false;
}
}
6.web.xml文件映射
<filter>
<filter-name>AuthFilter</filter-name>
<filter-class>***.***.***.web.filter.AuthFilter</filter-class>
<init-param>
<param-name>jwt_secret</param-name>
<param-value>acc3.0jwt_secret</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
7.测试方法
@GET
@Path("/test")
public ComResult test(@Context HttpServletRequest request) {
String userId = (String)request.getAttribute("userId");
return ComResult.success(userId);
}