一、需求如下
對指定的API路徑進行簽名認證,對于沒有指定的無需認證,認證具體到方法。
二、查閱資料與開發
1.了解JWT,實際上用的開源jjwt
2.編寫自定義注解
3.編寫攔截器,主要是攔截特定的url進行簽名驗證,這里解析請求的handler是否有包含自定義注解
確定思路后,開始編寫代碼
A、寫工具,我在網上找的,代碼不復雜,一看就懂,代碼如下/**
* @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"));
}
}
B、編寫注解/**
* @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 {
}
C。編寫攔截器/**
* @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);
}
}
D。配置攔截器/**
* @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);
}
}
E、在調用的controller方法中加上注解@RequireSignature
大功告成