1. API接口不安全的因素
开发者访问开放接口
多客户端访问接口
用户访问接口
接口传输
其他方面
2. 常见的保证接口安全的方式
2.1 AccessKey & SecretKey
这种设计一般用在开发接口的安全,以确保是一个合格的开发者
AccessKey: 开发者唯一标识
SecretKey: 开发者迷药
以阿里云相关产品为例:
2.2 认证和授权
从两个视角去看
第一:认证和授权,认证是访问这的合法性,授权是访问这的权限等级
第二:认证包括对客户端的认证和对用户的认证
2.3 Https
从接口传输安全的角度,防止接口数据明文传输
Http有以下安全性问题:
使用明文进行通信,内容可能会被窃听
不验证通信方的身份,通信方可能遭遇伪装
无法验证报文的完整性,报文可能被篡改
HTTPS
并不是新协议,而是让 HTTP
先和 SSL(Secure Sockets Layer)通信,再由 SSL
和 TCP
通信,也就是说 HTTPS
使用了隧道进行通信。
通过私用SSL,HTTPS具有了加密(防窃听)、认证(防伪装)和完整性保护(防篡改)
2.4 接口签名(加密)
接口签名(加密):主要防止请求参数被篡改,特别是安全要求比较高的接口,比如支付领域的接口
3. 实现案例
本例子采用AOP兰姐自定义注解方式实现,主要看实现的思路而已(签名的目的是要防止参数被篡改,就要对可能被篡改的参数签名)
3.1 定义注解
1 2 3 4
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Signature { }
|
3.2 AOP拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| @Aspect @Component public class SignAspect { private static final String SIGN_HEADER = "X-SIGN";
@Pointcut("execution(@org.example.annoations.Signature * *(..))") private void verifySignPointCut() { }
@Before("verifySignPointCut()") public void verify() throws BusinessException { HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); String sign = request.getHeader(SIGN_HEADER);
if (CharSequenceUtil.isBlank(sign)) { throw new BusinessException("no signature in header: " + SIGN_HEADER); }
try { String generatedSign = generatedSignature(request); if (!sign.equals(generatedSign)) { throw new BusinessException("invalid signature"); } } catch (Throwable throwable) { throw new BusinessException("invalid signature"); } }
public String generatedSignature(HttpServletRequest request) throws IOException { String bodyParam = null; if (request instanceof ContentCachingRequestWrapper) { bodyParam = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8); }
Map<String, String[]> requestParameterMap = request.getParameterMap();
String[] paths = null; ServletWebRequest webRequest = new ServletWebRequest(request, null); Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (!CollectionUtils.isEmpty(uriTemplateVars)) { paths = uriTemplateVars.values().toArray(new String[0]); }
return SignUtil.sign(bodyParam, requestParameterMap, paths); } }
|
3.3 过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@Slf4j public class RequestCachingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { boolean isFirstRequest = !isAsyncDispatch(request); HttpServletRequest requestWrapper = request; if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) { requestWrapper = new ContentCachingRequestWrapper(request); } try { filterChain.doFilter(requestWrapper, response); } catch (Exception e) { e.printStackTrace(); } } }
|
3.4 注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Configuration public class FilterConfig {
@Bean public RequestCachingFilter requestCachingFilter() { return new RequestCachingFilter(); }
@Bean public FilterRegistrationBean requestCachingFilterRegistration(RequestCachingFilter requestCachingFilter){ FilterRegistrationBean bean = new FilterRegistrationBean(requestCachingFilter); bean.setOrder(1); return bean; } }
|
3.5 定义签名工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
public class SignUtil {
private static final String DEFAULT_SECRET = "1qaz@WSX#$%&";
public static String sign(String body, Map<String, String[]> params, String[] paths) { StringBuilder sb = new StringBuilder(); if (CharSequenceUtil.isNotBlank(body)) { sb.append(body).append('#'); }
if (!CollectionUtils.isEmpty(params)) { params.entrySet() .stream() .sorted(Map.Entry.comparingByKey()) .forEach(paramEntry -> { String paramValue = String.join(",", Arrays.stream(paramEntry.getValue()).sorted().toArray(String[]::new)); sb.append(paramEntry.getKey()).append("=").append(paramValue).append('#'); }); }
if (ArrayUtil.isNotEmpty(paths)) { String pathValues = String.join(",", Arrays.stream(paths).sorted().toArray(String[]::new)); sb.append(pathValues); } return SecureUtil.sha256(String.join("#", DEFAULT_SECRET, sb.toString())); }
}
|
3.6 测试
1 2 3 4 5 6 7 8 9 10
| @RestController @RequestMapping("/sign") public class SignController {
@Signature @PostMapping("/test/{id}") public ResponseResult<String> test(@PathVariable String id) { return ResponseResult.success(String.join(",", id)); } }
|