一、需求如下
对指定的API路径进行签名认证,对于没有指定的无需认证,认证具体到方法。
二、查阅资料与开发
1.了解JWT,实际上用的开源jjwt
2.编写自定义注解
3.编写拦截器,主要是拦截特定的url进行签名验证,这里解析请求的handler是否有包含自定义注解
/**
* @Title: TokenUtils.java
* @Description:
* @Copyright: Copyright (c) 2018
* @Company:http://www.sinocon.cn
* @author Administrator
* @date 2018年8月21日
* @version 1.0
*/
package cn.sinocon.hive.utils;
import java.security.Key;
import java.util.Date;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import cn.hutool.core.date.DateUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* @Title: TokenUtils
* @Description:
* @author:Administrator
* @date 2018年8月21日
*/
public class TokenUtils {
/**
* 签名秘钥
*/
public static final String SECRET = "LHqDYnwpy7jzhmWdIy7EW3ER64mNlAGKRZWLKFvSKIyWWX";
/**
* 生成token
*
* @param id
* 一般传入userName
* @return
*/
public static String createJwtToken(String id) {
String issuer = "www.zuidaima.com";
String subject = "8vfu3wqEidZve2";
long ttlMillis = System.currentTimeMillis();
return createJwtToken(id, issuer, subject, ttlMillis);
}
/**
* 生成Token
*
* @param id
* 编号
* @param issuer
* 该JWT的签发者,是否使用是可选的
* @param subject
* 该JWT所面向的用户,是否使用是可选的;
* @param ttlMillis
* 签发时间
* @return token String
*/
public static String createJwtToken(String id, String issuer, String subject, long ttlMillis) {
// 签名算法 ,将对token进行签名
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成签发时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
// 通过秘钥签名JWT
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
// Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id).setIssuedAt(now).setSubject(subject).setIssuer(issuer)
.signWith(signatureAlgorithm, signingKey);
// if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
// Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
// Sample method to validate and read the JWT
public static Claims parseJWT(String jwt) {
// This line will throw an exception if it is not a signed JWS (as
// expected)
Claims claims = Jwts.parser().setSigningKey(DatatypeConverter.parseBase64Binary(SECRET)).parseClaimsJws(jwt)
.getBody();
return claims;
}
public static void main(String[] args) {
System.out.println(TokenUtils.createJwtToken("page=10"));
}
}
/**
* @Title: RequireSignature.java
* @Description:
* @Copyright: Copyright (c) 2018
* @Company:http://www.sinocon.cn
* @author Administrator
* @date 2018年8月18日
* @version 1.0
*/
package cn.sinocon.hive.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Title: RequireSignature
* @Description:
* @author:Administrator
* @date 2018年8月18日
*/
@Target({ElementType.METHOD})// 可用在方法名上
@Retention(RetentionPolicy.RUNTIME)// 运行时有效
public @interface RequireSignature {
}
/**
* @Title: LoginInterceptor.java
* @Description:
* @Copyright: Copyright (c) 2018
* @Company:http://www.sinocon.cn
* @author Administrator
* @date 2018年8月18日
* @version 1.0
*/
package cn.sinocon.hive.interceptor;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.sinocon.hive.annotation.RequireSignature;
import cn.sinocon.hive.utils.TokenUtils;
import io.jsonwebtoken.Claims;
/**
* @Title: LoginInterceptor
* @Description:
* @author:Administrator
* @date 2018年8月18日
*/
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {
public final static String ACCESS_TOKEN = "accessToken";
public final static String EXCEPTION_MSG = "signature does not match locally computed signature,error code:";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RequireSignature methodAnnotation = method.getAnnotation(RequireSignature.class);
// 有 @RequireSignature 注解,需要认证
if (ObjectUtil.isNotNull(methodAnnotation)) {
// 判断是否存在令牌信息,如果存在,则允许登录
String accessToken = request.getParameter(ACCESS_TOKEN);
if (StringUtils.isBlank(accessToken)) {
// 需要认证才行
throw new RuntimeException(EXCEPTION_MSG + "400003");
}
Claims claims = null;
try {
claims = TokenUtils.parseJWT(accessToken);
} catch (Exception e) {
throw new RuntimeException(EXCEPTION_MSG + "400005");
}
// 签名格式错误,请按照约定生成签名
String[] firstParam = claims.getId().split("=");
if (ObjectUtils.isEmpty(firstParam)) {
throw new RuntimeException(EXCEPTION_MSG + "400005");
}
// 签名被篡改
String parameter = request.getParameter(firstParam[0]);
if (!firstParam[1].equals(parameter)) {
throw new RuntimeException(EXCEPTION_MSG + "400006");
}
boolean validation = false;
// 获取签名生成的时间,签名有效10分钟
try {
long timeInMillis = DateUtil.calendar(Long.parseLong(claims.get("exp") + "")).getTimeInMillis();
validation = DateUtil.calendar(System.currentTimeMillis())
.getTimeInMillis() < (timeInMillis + 10 * 60 * 1000);
} catch (Exception e) {
throw new RuntimeException(EXCEPTION_MSG + "400005");
}
// 超时
if (validation) {
throw new RuntimeException(EXCEPTION_MSG + "400007");
}
}
return super.preHandle(request, response, handler);
}
}
/**
* @Title: ResourceConfig.java
* @Description:
* @Copyright: Copyright (c) 2018
* @Company:http://www.sinocon.cn
* @author Administrator
* @date 2018年8月6日
* @version 1.0
*/
package cn.sinocon.hive.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import cn.sinocon.hive.interceptor.LoginInterceptor;
/**
* @Title: ResourceConfig
* @Description:
* @author:Administrator
* @date 2018年8月6日
*/
@Configuration
public class ResourceConfig implements WebMvcConfigurer {
/*
* 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页
*
*
Title: addViewControllers
*
*
Description:
*
* @param registry
*
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#
* addViewControllers(org.springframework.web.servlet.config.annotation.
* ViewControllerRegistry)
*
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
/* (non-Javadoc)
*
Title: addInterceptors
*
Description: 拦截器配置
* @param registry
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#addInterceptors(org.springframework.web.servlet.config.annotation.InterceptorRegistry)
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/**");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
在调用的controller方法中加上注解@RequireSignature