请叫我小C
2018-08-22 15:32:27
SpringBoot RestFull API签名
一、需求如下
对指定的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 { /* * 默认首页的设置,当输入域名是可以自动跳转到默认指定的网页 * * <p>Title: addViewControllers</p> * * <p>Description: </p> * * @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) * <p>Title: addInterceptors</p> * <p>Description: 拦截器配置</p> * @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
大功告成
评论
最近浏览
java小书童 LV17
2021年1月8日
q847673141 LV2
2020年9月28日
EricLan LV5
2020年6月18日
howes_hao
2020年5月18日
暂无贡献等级
756124136
2020年5月6日
暂无贡献等级
fsq647218
2020年4月24日
暂无贡献等级
18131371836 LV2
2020年3月4日
guixin LV15
2019年12月2日
ITkuangren LV8
2019年10月31日
xxljxgz LV21
2019年10月14日