When developing the background API interface with SpringBoot, what are the interface insecurity factors? How is it usually solved? This article mainly introduces the insecure factors of API interfaces and common ways to ensure the security of interfaces , and focuses on how to sign interfaces . @pdai
SpringBoot Interface - What are the insecure factors of API interface? How to sign an interface?
Prepare knowledge points
It is recommended to understand from the perspective of the overall security system of the interface, such as what insecure factors exist, and knowledge points such as encryption and decryption.
What are the insecure factors of API interface?
Here are some unsafe factors from a system perspective:
Developer access to open interfaces
- Is it a legitimate developer?
Multiple Client Access Interface
- Is it a legitimate client?
User access interface
- Is it a legitimate user?
- Do you have permission to access the interface?
interface transmission
- HTTP clear text transmission of data?
Other aspects
- Interface replay, the interface described above is idempotent
- Interface timeout, plus timestamp control?
- ...
Common way to ensure interface security?
In view of the insecure factors of the above interfaces, here are some typical ways to ensure the security of interfaces.
AccessKey&SecretKey
This design is generally used to secure the development interface to ensure that it is a legitimate developer .
- AccessKey: The developer's unique identifier
- SecretKey: developer key
Take Alibaba Cloud related products as an example
Authentication and Authorization
from two perspectives
- First: authentication and authorization , authentication is the legitimacy of the visitor, and authorization is the authority classification of the visitor;
- Second: The authentication includes the authentication of the client and the authentication of the user ;
- Authentication for clients
Typical is AppKey&AppSecret, or ClientId&ClientSecret, etc.
For example, the client credential mode of the oauth2 protocol
https://api.xxxx.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
The grant_type parameter equal to client_credentials indicates the client credentials method, client_id is the client id, and client_secret is the client secret.
After returning the token, access other interfaces through the token.
- Authentication and authorization for users
For example, the authorization code mode (authorization code) and the password mode (resource owner password credentials) of the oauth2 protocol
https://api.xxxx.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID&scope=read
The grant_type parameter is equal to password to indicate the password mode, client_id is the client id, username is the user name, and password is the password.
(PS: password mode is only used when authorization code mode is not available, here is just an example)
The optional parameter scope indicates the permission scope of the application. (For related development frameworks, please refer to spring security, Apache Shiro, SA-Token , etc.)
https
From the perspective of interface transmission security, prevent interface data from being transmitted in plaintext, see here for details
HTTP has the following security issues:
- Use clear text to communicate, the content may be eavesdropped;
- Without verifying the identity of the communicating party, the identity of the communicating party may be disguised;
- The integrity of the message cannot be proven, and the message may be tampered with.
HTTPs is not a new protocol, but let HTTP communicate with SSL (Secure Sockets Layer) first, and then communicate with SSL and TCP, that is to say, HTTPs uses tunnels for communication.
By using SSL, HTTPs has encryption (anti-eavesdropping), authentication (anti-masquerading), and integrity protection (anti-tampering).
Interface signature (encryption)
Interface signature (encryption), mainly to prevent request parameters from being tampered with. Especially the interface with high security requirements, such as the interface in the payment field.
- The main process of signing
First, we need to assign a private key to the client for URL signature encryption. The general signature algorithm is as follows:
1. First, alphabetically sort the request parameters by key and put them into an ordered set (for other parameters, please refer to the subsequent supplementary part);
2. Use & to connect the sorted array key-value pairs to form a parameter string for encryption;
3. Add the private key before or after the encrypted parameter string, then encrypt it with an encryption algorithm to get the sign, and then send it to the server along with the request interface.
E.g:
https://api.xxxx.com/token?key=value&timetamp=xxxx&sign=xxxx-xxx-xxx-xxxx
After the server receives the request, it uses the same algorithm to obtain the sign of the server, and compares whether the sign of the client is consistent. If the request is consistent, the request is valid; if it is inconsistent, the specified error message is returned.
- Supplement: Sign what?
- It mainly includes the request parameters, which is the most important part. The purpose of the signature is to prevent the parameters from being tampered with, and it is necessary to sign the parameters that may be tampered with ;
- At the same time, it is considered that the source of the request parameters may be in the request path path, in the request header, and in the request body.
- If AppKey&AppSecret is assigned to the client, the signature calculation can also be added;
- Taking into account other idempotent, token invalidation, etc., the involved parameters will also be added to the signature, such as timestamp, serial number nonce, etc. (these parameters may come from the header)
- Supplement: Signature Algorithm?
Generally involved in this piece, it mainly includes three points: key, signature algorithm, signature rule
- Key secret : The secret agreed by the front and back ends. It should be noted here that the front end may not be able to properly store the secret, such as SPA single-page applications;
- Signature algorithm : It does not have to be a symmetric encryption algorithm. Symmetry is to parse the sign in reverse. Here, the same algorithm and rules are used to calculate the sign, and compare whether the sign passed from the front end is consistent.
- Signature rules : such as multiple salt encryption, etc.;
PS: Some readers will ask, it is possible for us to obtain keys, algorithms and rules from some clients (such as obtaining keys, algorithms and rules from js generated by a front-end SPA single-page application), so what is the significance of the signature? I think signature is a means rather than an end. Signature is a means to increase the difficulty of attackers' attacks, at least it can resist most simple attacks, plus other prevention methods (serial number, timestamp, token, etc.) to further Just increase the difficulty of the attack.
- Supplement: Is signing and encryption the same thing?
Strictly speaking not the same thing:
- The signature is calculated by calculating the sign according to the specified algorithm and rules for the parameters, and finally the front and back ends use the same algorithm to calculate whether the sign is consistent to prevent parameter tampering, so you can see that the parameter is in plaintext, but an extra calculated value is added. sign.
- Encryption is to encrypt the parameters of the request and decrypt the back-end; at the same time, in some cases, the returned response is also encrypted, and the front-end decrypts; there is a process of encryption and decryption, so the idea must be the form of symmetric encryption + time Stamp interface timeliness, etc.
- Supplement: Where is the signature placed?
The signature can be placed in the request parameters (path, body, etc.), or more elegantly in the HEADER, such as X-Sign (usually third-party header parameters start with X-)
- Supplement: How does the big factory open platform do it? Which can you learn from?
Take Tencent Open Platform as an example, please refer to the description of Tencent Open Platform's third-party application signature parameter sig
Implementation case
This example is implemented by AOP intercepting custom annotations, which mainly depends on the idea of implementation (the purpose of signature is to prevent parameters from being tampered with, and it is necessary to sign parameters that may be tampered with). @pdai
define annotations
package tech.pdai.springboot.api.sign.config.sign;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author pdai
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Signature {
}
AOP interception
Here you can see that all parameter points that may be modified by the user need to be signed according to the rules
package tech.pdai.springboot.api.sign.config.sign;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import cn.hutool.core.text.CharSequenceUtil;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.util.ContentCachingRequestWrapper;
import tech.pdai.springboot.api.sign.config.exception.BusinessException;
import tech.pdai.springboot.api.sign.util.SignUtil;
/**
* @author pdai
*/
@Aspect
@Component
public class SignAspect {
/**
* SIGN_HEADER.
*/
private static final String SIGN_HEADER = "X-SIGN";
/**
* pointcut.
*/
@Pointcut("execution(@tech.pdai.springboot.api.sign.config.sign.Signature * *(..))")
private void verifySignPointCut() {
// nothing
}
/**
* verify sign.
*/
@Before("verifySignPointCut()")
public void verify() {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String sign = request.getHeader(SIGN_HEADER);
// must have sign in header
if (CharSequenceUtil.isBlank(sign)) {
throw new BusinessException("no signature in header: " + SIGN_HEADER);
}
// check signature
try {
String generatedSign = generatedSignature(request);
if (!sign.equals(generatedSign)) {
throw new BusinessException("invalid signature");
}
} catch (Throwable throwable) {
throw new BusinessException("invalid signature");
}
}
private String generatedSignature(HttpServletRequest request) throws IOException {
// @RequestBody
String bodyParam = null;
if (request instanceof ContentCachingRequestWrapper) {
bodyParam = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8);
}
// @RequestParam
Map<String, String[]> requestParameterMap = request.getParameterMap();
// @PathVariable
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);
}
}
Request encapsulation
package tech.pdai.springboot.api.sign.config;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
@Slf4j
public class RequestCachingFilter extends OncePerRequestFilter {
/**
* This {@code doFilter} implementation stores a request attribute for
* "already filtered", proceeding without filtering again if the
* attribute is already there.
*
* @param request request
* @param response response
* @param filterChain filterChain
* @throws ServletException ServletException
* @throws IOException IOException
* @see #getAlreadyFilteredAttributeName
* @see #shouldNotFilter
* @see #doFilterInternal
*/
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull 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();
}
}
}
register
package tech.pdai.springboot.api.sign.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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;
}
}
implement interface
package tech.pdai.springboot.api.sign.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.pdai.springboot.api.sign.config.response.ResponseResult;
import tech.pdai.springboot.api.sign.config.sign.Signature;
import tech.pdai.springboot.api.sign.entity.User;
/**
* @author pdai
*/
@RestController
@RequestMapping("user")
public class SignTestController {
@Signature
@PostMapping("test/{id}")
public ResponseResult<String> myController(@PathVariable String id
, @RequestParam String client
, @RequestBody User user) {
return ResponseResult.success(String.join(",", id, client, user.toString()));
}
}
interface test
body parameter
Without X-SIGN
If X-SIGN is wrong
If X-SIGN is correct
Sample source code
https://github.com/realpdai/tech-pdai-spring-demos
more content
Say goodbye to fragmented learning, one-stop systematic learning without routines Back-end development: Java full-stack knowledge system (https://pdai.tech)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。