JWT是基于token的身份认证的方案。
json web token全称。可以保证安全传输的前提下传送一些基本的信息,以减轻对外部存储的依赖,减少了分布式组件的依赖,减少了硬件的资源。
可实现无状态、分布式的Web应用授权,jwt的安全特性保证了token的不可伪造和不可篡改。
本质上是一个独立的身份验证令牌,可以包含用户标识、用户角色和权限等信息,以及您可以存储任何其他信息(自包含)。任何人都可以轻松读取和解析,并使用密钥来验证真实性。
编写JWT工具类 JWTUtils
package com.fm.vege.base.util; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Date; public class JWTUtils { private static final String SECRET = "QAZwsx1234"; private static final String ISS_USER = "jwt.auth"; private static final String ID_CLAIM = "id"; /** * 默认过期2小时 */ private static final long EXPIRE_TIME = 2 * 60 * 60 * 1000; private static final Logger logger = LoggerFactory.getLogger(JWTUtils.class); /** * 生成用户token * @param playLoad * @return String */ public static String sign(PayLoadInfo playLoad){ Algorithm algorithm = Algorithm.HMAC256(SECRET); if(playLoad.getExp() == null){ //设置默认过期时间 playLoad.setExp(System.currentTimeMillis() + EXPIRE_TIME); } if(playLoad.getUserId() == null){ logger.error("生成token失败,userId不得为空!"); return ""; } if(playLoad.getUserId() == null){ logger.error("生成token失败,userType不得为空!"); return ""; } if(playLoad.getUserId() == null){ logger.error("生成token失败,userName不得为空!"); return ""; } String face = ""; if(playLoad.getFace() != null){ face = playLoad.getFace(); } String token = JWT.create() .withIssuer(ISS_USER). withIssuedAt(new Date(System.currentTimeMillis())). withExpiresAt(new Date(playLoad.getExp())). withClaim("username",playLoad.getUsername()). withClaim("face",face). withClaim("userType",playLoad.getUserType()). withClaim(ID_CLAIM,playLoad.getUserId()). sign(algorithm); return token; } /** * 校验token * * @param token token值 * @return boolean */ public static boolean verifyToken(String token) { try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) .withIssuer(ISS_USER) .build(); verifier.verify(token); return true; } catch (JWTVerificationException e) { logger.error("verifyToken 校验token失败:{}",e.getMessage()); return false; } catch (Exception e) { return false; } } /** * 校验token 并返回 DecodedJWT * * @param token token值 * @return DecodedJWT */ public static DecodedJWT verifyAndReturn(String token) { try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) .withIssuer(ISS_USER).build(); return verifier.verify(token); } catch (JWTVerificationException e) { logger.error("verifyAndReturn 校验token失败:{},id = {}",e.getMessage()); return null; } catch (Exception e) { return null; } } /** * 校验并返回用户类型 * @param token * @return Integer */ public static Integer verifyAndReturnUserType(String token) { try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) .withIssuer(ISS_USER).build(); return verifier.verify(token).getClaim("userType").asInt(); } catch (JWTVerificationException e) { logger.error("verifyAndReturn 校验token失败:{},id = {}",e.getMessage()); return null; } catch (Exception e) { return null; } } /** * 校验token * * @param token token值 * @param id 用户的id * @return boolean */ public static boolean verify(String token, Long id) { try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)) .withIssuer(ISS_USER) .withClaim(ID_CLAIM, id) .build(); verifier.verify(token); return true; } catch (JWTVerificationException e) { logger.error("verify 校验token失败:{},id = {}",e.getMessage(),id); return false; } catch (Exception e) { return false; } } /** * 获取用户id * * @param token token值 * @return java.lang.Long */ public static Long getId(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(ID_CLAIM).asLong(); } catch (JWTDecodeException e) { logger.error("获取用户id失败:{}",e.getMessage()); return null; } catch (Exception e) { return null; } } /** * 获取颁发时间 * * @param token token值 * @return java.util.Date */ public static Date getIssuedDate(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getIssuedAt(); } catch (JWTDecodeException e) { logger.error("获取颁发时间失败:{}",e.getMessage()); return null; } catch (Exception e) { return null; } } /** * 获取过期时间 * * @param token token值 * @return java.util.Date */ public static Date getExpireDate(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getExpiresAt(); } catch (JWTDecodeException e) { logger.error("获取过期时间失败:{}",e.getMessage()); return null; } catch (Exception e) { return null; } } /** * 判断是否过期 * * @param token token值 * @return boolean */ public static boolean isExpire(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getExpiresAt().compareTo(new Date()) <= 0 ? true : false; } catch (JWTDecodeException e) { return true; } catch (Exception e) { return true; } } }
PayLoadInfo:
package com.fm.vege.base.util; import lombok.Data; @Data public class PayLoadInfo { /** * iss: 该JWT的签发者,是否使用是可选的; * sub: 该JWT所面向的用户,是否使用是可选的; * aud: 接收该JWT的一方,是否使用是可选的; * exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的; * iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的; * 其他还有: * nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的; */ /** * 必填 */ private Long userId; /** * 用户类型 1 管理员 2 卖家 3 普通用户 */ private Integer userType; /** * 用户名 */ private String username; /** * 头像地址 */ private String face; /** * 可选 */ private String iss; /** * 可选 */ private Long exp; /** * 可选 */ private Long iat; }
登录成功后生成token签名
网页用户登录成功后,生成并返回token,然后存储到浏览器cookie,以后每次请求需要携带该token进行身份认证
@Override public Rsp<SellerLoginBO> loginSeller(LoginReqBO reqBO) { if(reqBO.getUsername() == null || reqBO.getPassword() == null){ throw new BusinessException(BaseConstants.RSP_ERROR,"用户名或密码不得为空!"); } SellerPO sellerPO = new SellerPO(); sellerPO.setLoginName(reqBO.getUsername()); sellerPO.setLoginPwd(reqBO.getPassword()); SellerPO seller = sellerMapper.selectByLoginNameAndPass(sellerPO); if(seller == null){ throw new BusinessException(BaseConstants.RSP_ERROR,"用户名或密码不正确!"); }else{ PayLoadInfo payLoadInfo = new PayLoadInfo(); payLoadInfo.setUserId(seller.getSellerId()); payLoadInfo.setUserType(DictionaryUserField.USER_TYPE_SELLER.getValue()); payLoadInfo.setUsername(sellerPO.getLoginName()); //模拟用户头像 if(sellerPO.getSellerPhoto() == null){ payLoadInfo.setFace("/img/face.jpg"); }else{ payLoadInfo.setFace(sellerPO.getSellerPhoto()); } String token = JWTUtils.sign(payLoadInfo); if("".equals(token)){ throw new BusinessException(BaseConstants.RSP_ERROR,"登录失败,token创建失败!"); } SellerLoginBO loginBO = new SellerLoginBO(); Rsp<SellerLoginBO> rsp = new Rsp<>(); BeanUtils.copyProperties(seller,loginBO); loginBO.setToken(token); rsp.setData(loginBO); rsp.setRspCode(BaseConstants.RSP_SUCCESS); rsp.setRspDesc("login successful!"); return rsp; } }
编写网关拦截器,进行身份认证
在zuul-router工程新建拦截器
@Component
public class AuthFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Override
public String filterType() {
/**
* 这里很重要,设置拦截类型为post请求之前
*/
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
/**
* 拦截器到优先级,数字越大的优先执行
*/
return 1;
}
/**
* 定义忽略的一些请求,比如登录之类的请求 不需要走拦截器认证
*/
private static List<String> ignoreApi = null;
static {
ignoreApi = Lists.newArrayList();
ignoreApi.add("/user-api/api/login/admin");
ignoreApi.add("/user-api/api/login/seller");
ignoreApi.add("/user-api/api/register/seller");
ignoreApi.add("/user-api/api/register/user");
ignoreApi.add("/user-api/api/sms/send");
ignoreApi.add("/user-api/api/sms/get");
}
@Override
public boolean shouldFilter() {
//共享RequestContext,上下文对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
logger.info("request uri : {}",request.getRequestURI());
//不需要权限校验URL
for (String api : ignoreApi) {
if (api.equalsIgnoreCase(request.getRequestURI())) {
return false;
}
}
return true;
}
@Override
public Object run() throws ZuulException {
//JWT
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//token对象,有可能在请求头传递过来,也有可能是通过参数传过来,实际开发一般都是请求头方式
String token = request.getHeader("token");
if (StringUtils.isBlank((token))) {
token = request.getParameter("token");
}
logger.info("页面传来的token值为:{}", token);
//登录校验逻辑 如果token为null,则直接返回客户端,而不进行下一步接口调用
if (StringUtils.isBlank(token)) {
// 过滤该请求,不对其进行路由
requestContext.setSendZuulResponse(false);
//返回错误代码
requestContext.setResponseStatusCode(401);
return null;
} else {
String requestURI = request.getRequestURI();
Integer userType = JWTUtils.verifyAndReturnUserType(token);
if (userType == null) {
logger.info("非法请求:{}", request.getRequestURI());
// 过滤该请求,不对其进行路由
requestContext.setSendZuulResponse(false);
//返回错误代码
requestContext.setResponseStatusCode(401);
return null;
}
logger.info("用户类型:{} , userType = {} ", DictionaryUserField.getDescByValue(userType),userType);
//管理员
if (DictionaryUserField.USER_TYPE_ADMIN.getValue().equals(userType)) {
} else if (DictionaryUserField.USER_TYPE_SELLER.getValue().equals(userType)) {
//卖家
if (request.getRequestURI().contains("/api/admin")) {
logger.info("非法请求,禁止普通用户访问接口:{}", request.getRequestURI());
// 过滤该请求,不对其进行路由
requestContext.setSendZuulResponse(false);
//返回错误代码
requestContext.setResponseStatusCode(401);
return null;
}
} else if (DictionaryUserField.USER_TYPE_PT.getValue().equals(userType)) {
//普通用户
}
return null;
}
}
}