一、需求如下


对指定的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