последний разКак обеспечить безопасность данных при взаимодействии интерфейсного и внутреннего API? 》В этой статье мы представили, как единообразно обрабатывать шифрование и дешифрование данных в среде Spring Boot. Для шифрования запроса выполняется только автоматическое шифрование запроса POST.Сегодня я продолжу рассказывать, как обеспечить безопасность запроса GET?
Сначала рассмотрим простой GET-запрос:
Programmerworld.com/user?Тогда = есть…
Прежде всего, видно, что мы видим, что параметр имени находится в открытом виде.Если требования безопасности очень высоки, рекомендуется использовать для запроса POST-запрос.Мы зашифровали все параметры POST-запроса.
Либо GET, либо POST могут быть подписаны
Открытый текст не имеет значения, ключ в том, что я копирую этот запрос в браузер, чтобы открыть его, изменить имя на другое значение, и, если он существует, он также может вернуть результат. Проблема здесь, параметры изменены и бэкэнд не может это распознать, это первая проблема.
Вторая проблема в том, что этот запрос можно использовать бесконечно долго, то есть, если вы запросите этот адрес завтра, он все равно вернет результат.На самом деле это тоже нужно контролировать.Конечно, есть много способов это контролировать. Сегодня мы представим относительно простой способ управления .
первый способ
Добавляем подпись к параметрам, согласовываем ключ на переднем и заднем концах, объединяем параметры в строку в алфавитном порядке, затем соединяем ключ и, наконец, шифруем его с помощью MD5 или SHA и, наконец, получаем зашифрованную подпись, которая передается серверной части в качестве параметра для проверки.
Например:
name=yinjihuan&sign=MD5(name=yinjihuan+key)
На бэкенде мы можем равномерно проверить в фильтре, получить значение признака параметра, получить все параметры запроса, а также сгенерировать новый признак так, как генерит признак фронтенд, и сравнить два знаки, если они равны, это говорит о том, что Параметры не подделаны.
Чтобы предотвратить использование запроса несколько раз, мы обычно добавляем Timestamp в момент запроса на знак, и сервер будет судить разницу времени. Если она может продолжать выполнять в течение 10 минут, конечно, вы Можно пойти на эту 10 минут самостоятельно. Регулировка, немного дольше, в основном является проблемой, что время между клиентом и сервером отличается. Конечно, эта ситуация нельзя полностью избежать.
второй способ
Второй метод относительно прост, потому что мы говорили о шифровании и расшифровке запрошенных данных ранее.Теперь, когда у нас есть зашифрованный ключ и алгоритм шифрования, мы можем фактически зашифровать подписанный контент с помощью нашего алгоритма шифрования.Метод md5, использованный выше Не очень безопасный, md5 можно взломать.
В то же время, поскольку я использую axios для запроса данных, я могу использовать перехватчик запросов, чтобы единообразно подписывать запрос перед запросом, без необходимости обрабатывать его отдельно в каждом месте.
При использовании запроса на получение мы используем следующий способ:
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
Затем в перехватчике запросов мы можем получить всю информацию о параметрах текущего запроса через params, здесь мы не используем сплайсинг, а напрямую добавляем в params signTime (время подписи), а затем шифруем все параметры для получения знака, передается в фоновый режим через заголовок запроса.
В это время данные в фоновом режиме представляют собой информацию о параметрах + время подписи, например: {имя: "yjh",signTime:19210212121212}, подпись представляет собой содержимое, зашифрованное с помощью {имя:"yjh",signTime:19210212121212} .
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
if (config.method == "get"){
var newParams = config.params;
console.log(newParams);
if (newParams == undefined) {
newParams = new Object();
}
newParams.signTime = new Date().getTime();
config.headers.sign = EncryptData(JSON.stringify(newParams));
console.log(JSON.stringify(config));
}
if (config.method == "post"){
var newParams = new Object();
newParams.signTime = new Date().getTime();
config.headers.sign = EncryptData(JSON.stringify(newParams));
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
Бэкэнда может быть подписана в фильтре, код выглядит следующим образом:
/**
* 请求签名验证过滤器<br>
*
* 请求头中获取sign进行校验,判断合法性和是否过期<br>
*
* sign=加密({参数:值, 参数2:值2, signTime:签名时间戳})
* @author yinjihuan
*
* @about http://cxytiandi.com/about
*
*/
public class SignAuthFilter implements Filter {
private EncryptProperties encryptProperties;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
ServletContext context = filterConfig.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
encryptProperties = ctx.getBean(EncryptProperties.class);
}
@SuppressWarnings("unchecked")
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (req.getMethod().equals("OPTIONS")) {
chain.doFilter(request, response);
return;
}
resp.setCharacterEncoding("UTF-8");
String sign = req.getHeader("sign");
if (!StringUtils.hasText(sign)) {
PrintWriter print = resp.getWriter();
print.write("非法请求:缺少签名信息");
return;
}
try {
String decryptBody = AesEncryptUtils.aesDecrypt(sign, encryptProperties.getKey());
Map<String, Object> signInfo = JsonUtils.getMapper().readValue(decryptBody, Map.class);
Long signTime = (Long) signInfo.get("signTime");
// 签名时间和服务器时间相差10分钟以上则认为是过期请求,此时间可以配置
if ((System.currentTimeMillis() - signTime) > encryptProperties.getSignExpireTime() * 60000) {
PrintWriter print = resp.getWriter();
print.write("非法请求:已过期");
return;
}
// POST请求只处理时间
// GET请求处理参数和时间
if(req.getMethod().equals(HttpMethod.GET.name())) {
Set<String> paramsSet = signInfo.keySet();
for (String key : paramsSet) {
if (!"signTime".equals(key)) {
String signValue = signInfo.get(key).toString();
String reqValue = req.getParameter(key).toString();
if (!signValue.equals(reqValue)) {
PrintWriter print = resp.getWriter();
print.write("非法请求:参数被篡改");
return;
}
}
}
}
} catch (Exception e) {
PrintWriter print = resp.getWriter();
print.write("非法请求:" + e.getMessage());
return;
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
Сначала мы оценим информацию о подписи, если нет, перехватим ее, затем выполним операцию расшифровки, получим время подписи, оценим срок действия и, наконец, сравним параметры в текущем запросе в цикле на основе информации о расшифрованных параметрах. Нет, это означает, что параметры были подделаны. Здесь я делаю относительно простой метод. Суждение значения преобразуется в строку для сравнения. Я не уверен, есть ли проблема с какими-то специальными типами данных. Вы можете сами поменять.
Код проверки, который я также инкапсулировал в свой spring-boot-starter-encrypt (добро пожаловать, звезда):GitHub.com/Йинджи Хуан/Да…
Просто настройте фильтр:
/**
* 注册签名验证过滤器
* @return
*/
@Bean
public FilterRegistrationBean signAuthFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new SignAuthFilter());
registrationBean.setUrlPatterns(Arrays.asList("/rest/*"));
return registrationBean;
}
Приведенный выше код может использоваться не всеми. Я лично считаю, что нет необходимости копировать мой код для экспериментов. Понимание идеи — ваш лучший выбор. Просто поделитесь, не распыляйте. . . . . . . . . . .
рекомендовать«Spring Boot + Vue, разделяющая интерфейс и серверную часть»Возьмите вас, чтобы поплавать в океане, где передняя и задняя части разделены.