Внешний код Vue Внутренний Java-код
После того, как пользователь входит в систему, используя имя пользователя и пароль, сервер возвращаетToken(обычноUUID),и ВоляToken-UserIdкключценить Правильная форма хранится в Служба кэшированияв сосуде。Сервер получаетпроситьпродолжить позжеTokenпроверять,Если токен не существует,иллюстрироватьпроситьниктоэффект。
Временная отметка — это текущая временная отметка, соответствующая моменту вызова клиентом интерфейса. Временная отметка используется для предотвращения DoS-атак. Когда хакер захватывает запрошенный URL-адрес для DoS-атаки, каждый раз при вызове интерфейса интерфейс определяет разницу между текущим системным временем сервера и меткой времени, переданной в интерфейсе. Если разница превышает определенное установленное время (. например, 5 минут), то этот запрос будет перехвачен. Если он находится в пределах установленного периода ожидания, предотвратить DoS-атаку невозможно. Механизм временных меток может только уменьшить продолжительность DoS-атак и сократить время атаки. Если хакер изменяет значение временной метки, это может быть обработано с помощью механизма подписи подписи.
DoS-атака с повтором
DoS — это сокращение от Denial of Service, что означает отказ в обслуживании. Атака, вызывающая DoS, называется DoS-атакой. Ее цель — помешать компьютеру или сети предоставлять нормальные услуги. Наиболее распространенные DoS-атаки включают атаки на пропускную способность компьютерной сети и атаки на подключение.
Под DoS-атакой подразумевается умышленная атака на недостатки в реализации сетевых протоколов или прямое и жестокое истощение ресурсов атакуемого объекта жестокими методами. Цель состоит в том, чтобы помешать целевому компьютеру или сети предоставлять нормальные услуги или доступ к ресурсам и сделать это. целевая системная служба перестает отвечать на запросы или даже выходит из строя, что в данном случае не предполагает компрометацию целевого сервера или целевого сетевого устройства. Эти сервисные ресурсы включают пропускную способность сети, емкость файловой системы, открытые процессы или разрешенные соединения. Такого рода атака приведет к нехватке ресурсов. Независимо от того, насколько высока скорость обработки данных компьютера, насколько велик объем памяти или насколько высока пропускная способность сети, последствий этой атаки невозможно избежать.
Одним словом, скрытая опасность повторного использования параметров запроса для подделки вторичных запросов.
nonce: случайное значение, которое случайно генерируется клиентом и передается в качестве параметра. Целью случайного значения является увеличение изменчивости подписи знака. Случайные значения обычно представляют собой комбинацию цифр и букв длиной 6 цифр. Не существует фиксированных правил для состава и длины случайных значений.
знак: обычно используется для подписей параметров, чтобы предотвратить несанкционированное изменение параметров. Наиболее распространенным является изменение важных конфиденциальных параметров, таких как сумма. Значение знака обычно сортирует все непустые параметры в порядке возрастания, а затем +токен+ключ+метка времени. +nonce (случайные числа) соединяются вместе, а затем шифруются с использованием определенного алгоритма шифрования и передаются как знак параметра в интерфейсе, либо знак можно поместить в заголовок запроса.
И сохраните подпись на кэш-сервере, и установите время ожидания, соответствующее времени ожидания временной метки (вот почему оно должно быть как можно короче. Согласованность двух значений времени может гарантировать, что независимо от того, находится ли оно в пределах временная метка указанного времени или внешний URL-адрес доступны только один раз). Одну и ту же подпись можно использовать только один раз. Если будет обнаружено, что эта подпись уже существует на сервере кэша, в обслуживании будет отказано. (Может эффективно предотвращать атаки повторного воспроизведения — DoS-атаки)
Если интерфейс захвачен хакером во время процесса передачи по сети, и значение параметра изменено, а затем интерфейс продолжает вызываться, хотя значение параметра изменяется, хакер не знает, как рассчитывается знак или что знак есть состав значений, я не знаю в каком порядке они склеены. Самое главное, что я не знаю ке в строке подписи. Что такое y, чтобы хакеры могли изменить значение параметра, но не могли изменить значение знака. Когда сервер вызывает интерфейс, он пересчитывает значение знака в соответствии с правилами знака, а затем сравнивает его со значением. знака параметра, переданного интерфейсом. Если они равны. Это означает, что значение параметра не было изменено. Если нет, это означает, что параметр был изменен незаконно, и интерфейс не будет выполнен.
Для некоторых важных операций, повторную отправку которых клиентом необходимо предотвратить (например, неидемпотентные важные операции), конкретный метод заключается в сохранении знака в качестве ключа для повторного использования при первой отправке запроса и установите тайм-аут, тайм-аут и временную метку. Установленные различия одинаковы. Когда к одному и тому же запросу обращаются во второй раз, он сначала проверяет, существует ли знак в Redis. Если он существует, это доказывает, что он был отправлен повторно, и интерфейс больше не будет вызываться. Если знак удаляется на кэш-сервере из-за истечения срока действия, когда URL-адрес снова запрашивает сервер, время истечения срока действия токена совпадает со временем истечения срока действия знака. Истечение срока действия знака также означает, что знак будет удален. Срок действия токена истек, поэтому тот же URL-адрес. Доступ к серверу снова будет заблокирован из-за ошибки токена, поэтому время истечения срока действия знака и токена должно совпадать. Отклонение повторных вызовов гарантирует, что URL-адрес не может быть использован, даже если он будет перехвачен другими (например, для получения данных).
Для интерфейсов, повторную отправку которых необходимо предотвратить, вы можете настроить аннотации, чтобы отметить их.
Уведомление:
Иногда это будет слишком сложно, если будут использоваться все меры безопасности. В реальных проектах их необходимо адаптировать в соответствии с конкретными обстоятельствами. Например, вы можете использовать только механизм подписи, чтобы гарантировать, что информация не будет подделана. вы можете использовать токен только при предоставлении целевых услуг. Механизм в порядке. Как все это адаптировать, зависит от реальной ситуации в проекте и требований к безопасности интерфейса.
AES, 3DES, DES и т. д. подходят для шифрования и дешифрования больших объемов данных или файлов данных.
Такие как ОСА, Рабин. Шифрование с открытым ключом, расшифровка с закрытым ключом. Производительность низкая при шифровании и дешифровании больших объемов данных.
Подумайте над вопросом:
Если приложение можно зашифровать, как следует обрабатывать внешний интерфейс h5. Если ключ, участвующий в создании подписи, помещен на страницу, любой, кто посещает веб-сайт, может нажать F12, чтобы просмотреть исходный код и узнать ключ? , а также знает процесс генерации подписи, поэтому посредник может изменить параметры, а затем самостоятельно сгенерировать подпись.
Решение:
Передняя часть случайным образом генерирует строку перед вызовом интерфейса, затем шифрует ее с помощью открытого ключа rsa, помещает результат шифрования в ключ заголовка запроса, а затем использует случайно сгенерированную строку-ключ для участия в генерации знака и наконец выполняет вызов интерфейса.
первый шаг,
Предположим, что все отправленные или полученные данные представляют собой набор M, а параметры с непустыми значениями параметров в наборе M сортируются от меньшего к большему в соответствии с ASCII-кодом имени параметра (лексикографический порядок). Например, теперь есть URL = http://ip:port?a=1&b=3&c=4
stringA = a1b3c4
Особое внимание обратите на следующие важные правила:
Если есть тело запроса, атрибуты json в теле запроса будут отсортированы в соответствии с вышеуказанными правилами. Если URL-адрес вашего запроса содержит параметры пути и тело запроса, в расчете будут участвовать все параметры.
Шаг 2
В строке stringA + токен + ключ + ts (текущая временная метка) + nonce получите символ stringSignTemp и выполните операцию MD5 над stringSignTemp, а затем преобразуйте все символы полученной строки в верхний регистр, чтобы получить значение знака SignValue.
Функция знака заключается в предотвращении изменения параметров. Клиенту необходимо передать параметр знака при вызове сервера. Когда сервер отвечает клиенту, он также может вернуть клиенту знак, чтобы проверить, имеет ли возвращаемое значение. подверглись незаконному вмешательству. Алгоритм подписи, передаваемый клиентом, и алгоритм подписи, на который отвечает сервер, могут различаться.
Данные формата подписи (по возрастанию словаря) + токен + ключ (случайно сгенерированный секретный ключ) + ts (текущая временная метка) + nonce (случайное число)
Подробный код смотрите в начале статьи.
Конфигурационный файл
sign:
# Ключ, используемый для шифрования, участвует в шифровании подписи, а также может использоваться для шифрования данных.
# Поместите открытый ключ во внешний интерфейс на странице
rsa:
privateKey: MIICdgIBADANBgkqhkiG9w
уровень управления
/**
* Description: Уровень подписи токена управления
* @author shenguangyang
* @date 2021/01/20
*/
@RestController
@ResultBody
@RequestMapping("token")
public class TokenSignController {
@Resource
TokenSignService tokenSignService;
@GetMapping("find")
@ApiSafe(SafeType.SIGN)
public User findUser(@RequestParam("uid") String uid) {
User user = tokenSignService.findUser(uid);
return user;
}
@PostMapping("save")
@ApiSafe(SafeType.CRYPTO)
public void save(@RequestBody User user) {
System.out.println("Сохранить успешно. ---> " + user);
}
/**
* Вход пользователя
* @return token
*/
@PostMapping("login")
public String login(@RequestBody User user) {
return tokenSignService.login(user);
}
}
Отметьте аннотацию @ApiSafe для методов, доступ к которым возможен только после входа в систему. Если вам нужно войти в систему и добавить подпись для предотвращения подделки параметров и атак повторного воспроизведения, отметьте метод аннотацией @ApiSafe(SafeType.SIGN). Если вам нужно войти в систему и зашифровать параметры, отметьте метод аннотацией @ApiSafe(SafeType.CRYPTO).
Проект собран с использованием Springboot, с использованием перехватчиков и фильтров.
Текущий конец выдает запрос в двух ситуациях, обе из которых проходят через фильтры. RequestApiSafeFilter
Процесс запроса
Добавить подпись
Самый важный шаг при проверке подписи серверной частью — получение всех параметров в запросе. Получить параметры в URL-адресе относительно просто, но при получении параметров тела запроса есть ошибка. интерфейс через почтовый запрос и данные в формате json, серверной части необходимо прочитать данные через поток ввода данных, но их невозможно прочитать после чтения. Если в это время не выполняется никакая обработка, при этом будет сообщено об ошибке. Springboot читает входной поток при разборе параметров, потому что данные были прочитаны
Основная функция класса BodyReaderHttpServletRequestWrapper — копирование входного потока HttpServletRequest. В противном случае, после того как вы вынесете параметр body и проверите подпись, при переходе в Контроллер полученный параметр будет нулевым.
шифрование
DecryptRequest
добрый Окончательнокопировать ПонятноHttpServletRequest входной поток , В противном случае перейдите на уровень управленияполучатьпараметртакже станетnull
шифрование Алгоритм используетRSAасимметричное шифрование,Возможна замена в соответствии с потребностями вашего проекта.,Например, используя AES-шифрование
О фильтрах, возвращающих нелегальные запросы во фронтенд
Когда в фильтре встречается незаконный запрос, на страницу необходимо отправить ответ об ошибке. Однако мне не удалось записать страницу с помощью следующего кода.
response.setContentType("application/json; charset=UTF-8");
Result<Object> result = Result.failure(code, msg);
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(result));
Поэтому мое решение — записать результат ошибки в запрос, перехватить его через перехватчик, а затем вернуть результат ошибки на фронтенд-страницу.
фильтр
request.setAttribute("code",1002);
request.setAttribute("msg","Время ожидания соединения с сервером истекло!!!");
Перехватчик
/**
* Авторизоваться Перехватчик * @author shenguangyang
*/
@Component
public class Interceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(Interceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("request url = " + request.getRequestURL());
// код и сообщение передаются из фильтра
Object code = request.getAttribute("code");
if ((code != null ) && ((int)code != ResultEnum.SUCCESS.getCode())) {
String msg = (String) request.getAttribute("msg");
ApiUtils.result(response, (int) code, msg);
return false;
}
return true;
}
}
Разрешение конфликтов между Перехватчиком и междоменной конфигурацией в Springboot
Принцип решения: HTTP-запрос сначала проходит через фильтр, а затем после достижения сервлета обрабатывается Перехватчиком, поэтому мы можем Если Пучоккорс помещен в фильтр, он может выполняться с приоритетом над разрешением Перехватчик.
Пучок WebMvcConfigurer в интерфейсе addCorsMappings Удалите междоменную конфигурацию. Эта междоменная конфигурация предназначена для Перехватчика.конфигурации, проблемы возникнут, если в вашем проекте используется фильтр
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. добавить в Информация о конфигурации CORS
CorsConfiguration config = new CorsConfiguration();
//Какие оригинальные домены освобождены?
config.addAllowedOrigin("*");
//Отправлять ли Cookie
config.setAllowCredentials(true);
//Какие методы запроса выпущены?
config.addAllowedMethod("*");
//Какая исходная информация заголовка запроса высвобождается?
config.addAllowedHeader("*");
//2. добавить в пути карты
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. Вернуть новый CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
Для междоменной настройки Перехватчика, если в проекте используется фильтр, необходимо удалить эту конфигурацию и настроить класс междоменной конфигурации.Внедрите класс VolyaCorsFilter в контейнер.
// @Override
// public void addCorsMappings(CorsRegistry registry) {
// registry.addMapping("/**")
// .allowCredentials(true)
// .allowedHeaders("*")
// .allowedOrigins("*")
// .allowedMethods("*");
// }
из-за асинхронностипросить Будет отправлен первымOPTIONSпросить,поэтомуфильтрнужнопроситьфильтр
String m1 = request.getMethod();
if (HttpMethod.OPTIONS.toString().equals(m1)){
response.setStatus(HttpStatus.NO_CONTENT.value());
filterChain.doFilter(request, response);
return;
}
server:
port: 9090
spring:
#reidsconfiguration
# Индекс базы данных Redis (по умолчанию — 0)
redis:
database: 0
# Адрес сервера Redis
host: 192.168.1.110
# Порт подключения к серверу Redis
port: 6379
# Пароль подключения к серверу Redis (по умолчанию пуст)
password: flzx3000C!@)(
lettuce:
pool:
#Максимальное количество соединений в пуле соединений (используйте отрицательные значения, чтобы указать отсутствие ограничений)
max-active: 8
# Максимальное время ожидания блокировки пула соединений (используйте отрицательные значения, чтобы указать отсутствие ограничения)
max-wait: -1ms
# Максимальное количество простаивающих соединений в пуле соединений
max-idle: 8
# Минимальное количество простаивающих соединений в пуле соединений
min-idle: 0
sign:
# Ключ, используемый для шифрования, участвует в шифровании подписи, а также может использоваться для шифрования данных.
rsa:
privateKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJIMoK69fZKh3j4WAMIMdIVmGYA4ODlyexDoFmYKX5mzd1c01x
logging:
level:
# Осторожно Осторожно Осторожно Обязательно измените его на собственное имя пакета.
com.itvip666: debug
file:
path: logs
name: logs/assist-apisafe.log
clean-history-on-start: true
pattern:
console: "%d{yyyy-MM-dd} [%thread] %-5level %logger{50} ===> %msg%n"
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-assist</artifactId>
<groupId>com.itvip666</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>assist-apisafe</artifactId>
<dependencies>
<dependency>
<groupId>com.itvip666</groupId>
<artifactId>assist-common</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--jsonКонвертировать-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!--redisСвязанный-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
</project>
/**
* Description: аннотация безопасности apiинтерфейса. Отметка этой аннотации означает, что вам необходимо войти в систему для доступа к отмеченному интерфейсу метода.
* Этот интерфейс необходимо использовать с кешем Redis.
* @author shenguangyang
* @date 2021/01/22
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiSafe {
SafeType value() default SafeType.NONE;
}
SafeType
public enum SafeType {
/**
* никто
*/
NONE,
/**
* Подписи обычно используются для подписей параметров, чтобы предотвратить несанкционированное изменение параметров. Наиболее распространенным является изменение важных конфиденциальных параметров, таких как сумма.
* Значение знака обычно равно Воля. Все непустые параметры сортируются по возрастанию, а затем +token+key+ts+одноразовый. номер (случайное число)
* Соедините их вместе, а затем используйте определенный алгоритм шифрования для выполнения шифрования. Преимущество этого метода в том, что после взлома происходит модификация.
* значение параметра, а затем продолжить вызов интерфейса. Хотя значение параметра было изменено, злоумышленник не знает.
* Как рассчитывается знак, поэтому значение параметра можно изменить, но значение знака нельзя изменить при вызове сервера.
* Перед интерфейсом значение знака будет пересчитано в соответствии с правилами знака, а затем сравнено со значением параметра знака, переданного интерфейсом.
* Если оно равно, это означает, что значение параметра не было изменено. Если нет, это означает, что параметр был изменен незаконно, и никакая реальная ответная информация не будет возвращена.
*
* Если это страница h5, преступникам легко узнать, как сгенерировать подпись, поэтому им необходимо динамически получать секретный ключ. , Затем приступайте к шифрованию
* передача инфекции
*
* Решением по умолчанию является полученная Временная метка - текущий Временная метка > 120 , Другими словами, срок действия ссылки истекает через 2 минуты.
*
* Заголовок запроса обязательно должен содержать следующие параметры:
* token: жетон
* ts: 10Кусочек Временная метка (необходимый)
* nonce: 8Кусочекслучайное число,Цифры + буквы (необходимый)
* sign: знак (Необходимый,Если не использовать знак,Вы можете заполнить его случайно,Просто не может быть пусто)
*
* Данные формата подписи (по возрастанию словаря) + токен + ключ (случайно сгенерированный секретный ключ) + ts (текущая временная метка) + nonce (случайное число)
* Подумайте над вопросом:
* Если это приложение, то его можно вылечить шифрованием, но внешне Как бороться с интерфейсомh5,Если Воля участвует в генерации ключа знака,
* Размещение его на странице приведет к тому, что любой, кто посетит веб-сайт, нажмет F12, чтобы просмотреть источник. Тогда он узнает ключ и сгенерированный код.
* знакиз流程,поэтомусередина间者可к修改параметра затем регенерировать себязнак。
* Решение:
* внешний интерфейс случайным образом генерирует нит перед вызовом интерфейса,Тогда передай rsaоткрытый ключруководитьшифрованиеиметь дело с,Воляшифрованиерезультатпередача инфекцииприезжать
* Бэкэнд, бэкэнд хранится в кеше Redis, а срок действия установлен на 30 секунд.
* Конкретный рабочий процесс:
* 1. внешний интерфейс случайным образом генерирует нит,Тогда передай rsaоткрытый ключруководитьшифрование,Воляшифрованиерезультатпередача инфекцииприезжать后端,Использование серверной частичный закрытый ключ
* Расшифровка, результаты Воли сохраняются в кэше, срок годности 30с.
* 2. внешний интерфейс Воля Воля Все непустые параметры сортируются по возрастанию, а затем / тело (тело запроса) + token + key + ts(текущий Временная метка) + nonce выполнить сращивание
* md5шифрование
* первый шаг,
* Предположим, что все отправленные или полученные данные представляют собой набор M, а параметры с непустыми значениями параметров в наборе M Воля отсортированы от меньшего к большему согласно ASCII-коду имени параметра (лексикографический порядок),
* использоватьURLключценитьверноиз格式(Прямо сейчасkey1=value1&key2=value2…)Сращенный внитьstringA или тело (тело запроса)длянитьstringA。
* Особое внимание обратите на следующие важные правила:
* Имя параметра Коды ASCII сортируются от меньшего к большему (лексикографический порядок);
* Если значение параметра пустое, он не будет участвовать в подписи;
* Имена параметров чувствительны к регистру;
* Шаг 2,
* в строке Наконец сращено + token + key + ts(текущий Временная метка) + nonce получить stringSignTemp,
* И выполнить операцию MD5 над stringSignTemp, а затем преобразовать все символы нить, полученные Волей, в верхний регистр и получить значение знака SignValue.
* Шаг 3
* Воля token , sign , ts , nonce Поместите его в заголовок запроса для доступа к серверной части.
*/
SIGN,
/**
* шифрование
*/
CRYPTO
}
/**
* Description: Разрешение конфликтов между Перехватчиком и междоменной конфигурацией в Springboot
* Принцип решения: HTTP-запрос сначала проходит через фильтр, а затем после достижения сервлета обрабатывается Перехватчиком, поэтому мы можем
* Если Пучоккорс помещен в фильтр, он может выполняться с приоритетом над разрешением Перехватчик.
*
* Пучок WebMvcConfigurer в интерфейсе addCorsMappings Удалите междоменную конфигурацию. Эта междоменная конфигурация предназначена для Перехватчика.
* конфигурации, проблемы возникнут, если в вашем проекте используется фильтр
* @author shenguangyang
* @date 2021/01/22
*/
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. добавить в Информация о конфигурации CORS
CorsConfiguration config = new CorsConfiguration();
//Какие оригинальные домены освобождены?
config.addAllowedOrigin("*");
//Отправлять ли Cookie
config.setAllowCredentials(true);
//Какие методы запроса выпущены?
config.addAllowedMethod("*");
//Какая исходная информация заголовка запроса высвобождается?
config.addAllowedHeader("*");
//2. добавить в пути карты
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. Вернуть новый CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
/**
* Авторизоваться Перехватчик * @author shenguangyang
*/
@Component
public class Interceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(Interceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("request url = " + request.getRequestURL());
// код и сообщение передаются из фильтра
Object code = request.getAttribute("code");
if ((code != null ) && ((int)code != ResultEnum.SUCCESS.getCode())) {
String msg = (String) request.getAttribute("msg");
ApiUtils.result(response, (int) code, msg);
return false;
}
return true;
}
}
/**
* Описание: конфигурация Redis, EnableCaching включает кеширование.
*
* @author shenguangyang
* @date 2020/12/31
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
@Override
public KeyGenerator keyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getName());
stringBuilder.append(method.getName());
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
return stringBuilder.toString();
};
}
//Примечание: имя метода должно быть redisTemplate
/**
* Перепишите метод сериализации Redis и используйте метод Json:
* Когда наши данные хранятся в Redis, наши ключи и значения сериализуются в базу данных через сериализатор, предоставляемый Spring.
* RedisTemplate по умолчанию использует JdkSerializationRedisSerializer, а StringRedisTemplate по умолчанию использует StringRedisSerializer.
* Spring Data JPA предоставляет нам следующий сериализатор:
* GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、
* JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
* Здесь мы сами настраиваем RedisTemplate и определяем Serializer.
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// Установить сериализацию по умолчанию JSON-сериализация
// Сериализация значения параметра (значения) использует FastJsonRedisSerializer.
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
// redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
// Сериализация заданного ключа (ключа) использует StringRedisSerializer.
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
private Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// Этот элемент должен быть настроен, иначе будет сообщено об исключении java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
/*Устанавливаем срок действия кэша 120s*/
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(120))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
/**
* Description: Перехватчик
* @author shenguangyang
* @date 2021/01/20
*/
@Configuration
public class WebFilterConfig implements WebMvcConfigurer {
/**
* Чтобы Redis не сообщал о пустости, инициализируйте перед контекстом
*
* @return
*/
@Bean
public Interceptor getLoginHandlerInterceptor() {
return new Interceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(getLoginHandlerInterceptor());
registration.addPathPatterns("/**");
registration.excludePathPatterns("/**/login");
}
// /**
// * Для междоменной настройки Перехватчика, если в проекте используется фильтр, необходимо удалить эту конфигурацию и настроить класс междоменной конфигурации.
// * Внедрите класс VolyaCorsFilter в контейнер.
// * @param registry
// */
// @Override
// public void addCorsMappings(CorsRegistry registry) {
// registry.addMapping("/**")
// .allowCredentials(true)
// .allowedHeaders("*")
// .allowedOrigins("*")
// .allowedMethods("*");
// }
}
/**
* Description: Уровень подписи токена управления
* @author shenguangyang
* @date 2021/01/20
*/
@RestController
@ResultBody
@RequestMapping("token")
public class TokenSignController {
@Resource
TokenSignService tokenSignService;
@GetMapping("find")
@ApiSafe(SafeType.SIGN)
public User findUser(@RequestParam("uid") String uid) {
User user = tokenSignService.findUser(uid);
return user;
}
@PostMapping("save")
@ApiSafe(SafeType.CRYPTO)
public void save(@RequestBody User user) {
System.out.println("Сохранить успешно. ---> " + user);
}
/**
* Вход пользователя
* @return token
*/
@PostMapping("login")
public String login(@RequestBody User user) {
return tokenSignService.login(user);
}
}
package com.itvip666.apisafe.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.itvip666.apisafe.annotations.ApiSafe;
import com.itvip666.apisafe.annotations.SafeType;
import com.itvip666.apisafe.service.CacheService;
import com.itvip666.apisafe.util.ApiUtils;
import com.itvip666.apisafe.util.RSAUtils;
import com.itvip666.apisafe.util.SignUtils;
import com.itvip666.result.ResultEnum;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
/**
* Description: Фильтр расшифровки параметров запроса / знак<br>
* нуждаться Расшифроватьизметод,По соответствующему методу илидобрый добавить ваннотация@CryptoType(CryptoType.CRYPTO)<br>
* внешний Интерфейс шифрования будет параметром Пучок через режим шифрования AES в base64, «зашифровать»: шифрование Опубликовать контент<br>
* FIXME В настоящее время выполняется только шифрование параметра запроса, а возвращаемое значение не выполняется.
*
* @author shenguangyang
* @date 2021/01/22
*/
@WebFilter(value = "/*",filterName ="RequestApiSafeFilter" )
public class RequestApiSafeFilter extends OncePerRequestFilter implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(RequestApiSafeFilter.class);
@Autowired
CacheService cacheService;
/**
* используется дляшифрованиеучаствоватьзнакшифрованиеизkey
*/
@Value("${sign.rsa.privateKey}")
private String signKeyRsaPrivateKey;
/** набор карт методов */
private List<HandlerMapping> handlerMappings;
private static final String APPLICATION_JSON = "application/json";
/**
* Временная меткатайм-аутк及знаксуществовать缓存серединаиз存活时间
*/
private static final long EXPIRE_TIME = 60;
/** Шифрование и дешифрование RSA */
@Value("${sign.rsa.privateKey}")
private String rsaPrivateKey;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
String m1 = request.getMethod();
if (HttpMethod.OPTIONS.toString().equals(m1)){
response.setStatus(HttpStatus.NO_CONTENT.value());
filterChain.doFilter(request, response);
return;
}
Object handler = getHandler(request).getHandler();
if (handler instanceof HandlerMethod) {
// Есть ли аннотации к методу?
HandlerMethod method = (HandlerMethod) handler;
ApiSafe type = method.getMethodAnnotation(ApiSafe.class);
if (null == type) {
// Есть ли аннотации к классу?
type = method.getBeanType().getAnnotation(ApiSafe.class);
if (null == type) {
filterChain.doFilter(request, response);
return;
}
}
// получатьжетон,знак,и Временная метка
Строковый токен = request.getHeader("токен");
Строка ts = request.getHeader("ts");
// случайное число,внешний интерфейс Основные гарантии генерации случайных чиселзнакиз多变性
String nonce = request.getHeader("nonce");
String sign = request.getHeader("sign");
// Проверьте, является ли токен законным
String key = "user:token:" + token;
if (StringUtils.isEmpty(token) || !cacheService.hasKey(key)) {
request.setAttribute("code",1005);
request.setAttribute("msg","жетон失эффект,请重新Авторизоваться!!!");
filterChain.doFilter(request, response);
return;
}
// Определить, является ли это расшифровкой
// Если это шифрование, заголовок запроса не должен содержать nonce, ts , такие поля, как знак
if (type.value() == SafeType.CRYPTO) {
// Не пропускать расшифровку
filterChain.doFilter(new DecryptRequest(request), response);
return;
}
// Определите, являются ли обязательные параметры пустыми, и выполните последующие операции, если они не пусты.
if (StringUtils.isEmpty(sign) || StringUtils.isEmpty(ts) || StringUtils.isEmpty(nonce)){
request.setAttribute("code",1001);
request.setAttribute("msg","Недопустимый параметр!!!");
filterChain.doFilter(request, response);
return;
}
// Выполните операцию продления срока действия, чтобы определить, истек ли срок действия билета.
Object vaule = cacheService.find(key);
cacheService.delay(key , vaule , 10 * 60);
System.out.println(SignUtils.getTimestamp());
// 判断да否нуждатьсяпроверятьзнак
if (type.value() == SafeType.SIGN) {
// Предотвратите однократное чтение потока и его исчезновение. Поэтому мне нужен поток Воли, чтобы продолжать это писать.
HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
// дазнак
// Получить все параметры (включая URL и тело)
Map<String, String> allParams = ApiUtils.getAllParams(requestWrapper);
SignRequest signRequest = new SignRequest();
// знакпроверять失败会往проситьсередина写入数据
signRequest.verifySign(requestWrapper,allParams);
filterChain.doFilter(requestWrapper, response);
return;
}
}
filterChain.doFilter(request, response);
}
/**
* Получить целевой метод доступа
*
* @param request
* @return HandlerExecutionChain
* @throws Exception
*/
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (log.isTraceEnabled()) {
log.trace("Testing handler map [" + hm + "] in DispatcherServlet with name ''");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
/**
* знакпросить
*/
private class SignRequest {
/**
* проверятьзнакда否一致
*/
public void verifySign(HttpServletRequest request , Map<String,String> params) {
String ts = request.getHeader("ts");
String sign = request.getHeader("sign");
// keyЗависит отвнешний шифрование из интерфейса, шифрование через rsa
String key = request.getHeader("key");
try {
// Решите, стоит ли приходить снова (повтор атаки),существоватьповтор период временного окна , может только облегчить повторение атаки
if (SignUtils.getTimestamp() - Long.valueOf(ts) > EXPIRE_TIME){
request.setAttribute("code",1002);
request.setAttribute("msg","Время ожидания соединения с сервером истекло!!!");
return;
}
// [использовать знак хранится в кеше + повтор период временного окна]предотвращатьповтор атаки
if (isRepeatedSubmit(EXPIRE_TIME,sign)) {
request.setAttribute("code",1004);
request.setAttribute("msg","Пожалуйста, не отправляйте повторно!!!");
return;
}
try {
key = RSAUtils.decrypt(key, signKeyRsaPrivateKey);
} catch (Exception e) {
request.setAttribute("code", ResultEnum.ILLEGAL_PARAM.getCode());
request.setAttribute("msg",ResultEnum.ILLEGAL_PARAM.getMsg());
return;
}
// проверятьзнак
if (!SignUtils.checkSign(params , request , sign , key)){
request.setAttribute("code",1003);
request.setAttribute("msg","никтоэффектиззнак");
}
} catch (Exception e) {
request.setAttribute("code",1006);
request.setAttribute("msg",e.getMessage());
}
}
/**
* Определите, является ли это дублирующейся отправкой
* Каждый раз, когда приходит запрос, redis запрашивается по ключу. Если он существует, значит, он отправляется повторно и выдается результат. Если его нет, он отправляется нормально и Воляключ сохраняется в redis.
* @param timeout Тайм-аут кэша Единица измерения: с.
* @param sign знак
*/
protected boolean isRepeatedSubmit(long timeout , String sign) throws Exception {
String key = "sign:" + sign;
Object obj = cacheService.find(key);
if (obj == null) {
cacheService.save(key , "" , timeout);
return false;
}
return true;
}
}
/**
* Расшифровать инкапсуляцию запроса
*
* @author ldy
*
*/
private class DecryptRequest extends HttpServletRequestWrapper {
private static final String APPLICATION_JSON = "application/json";
/** Сбор карт всех параметров */
private Map<String, String[]> parameterMap;
/** входной поток */
private InputStream inputStream;
public DecryptRequest(HttpServletRequest request) throws IOException {
super(request);
String contentType = request.getHeader("Content-Type");
log.debug("DecryptRequest -> contentType:{}", contentType);
String encrypt = null;
if (null != contentType && contentType.contains(APPLICATION_JSON)) {
// json
ServletInputStream io = request.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = io.read(buffer)) != -1) {
os.write(buffer, 0, length);
}
byte[] bytes = os.toByteArray();
encrypt = (String) JSON.parseObject(new String(bytes)).get("encrypt");
} else {
// url
encrypt = request.getParameter("encrypt");
}
log.debug("DecryptRequest -> encrypt:{}", encrypt);
// Расшифровать
String params = decrypt(encrypt);
// Расшифровать失败
if (StringUtils.isEmpty(params)) {
request.setAttribute("code",1009);
request.setAttribute("msg","Сервер отклоняет запрос");
return;
}
if (null != contentType && contentType.contains(APPLICATION_JSON)) {
if (this.inputStream == null) {
this.inputStream = new DecryptInputStream(new ByteArrayInputStream(params.getBytes()));
}
}
parameterMap = buildParams(params);
}
private String decrypt(String encrypt) throws IOException {
try {
// Расшифровать
return RSAUtils.decrypt(encrypt,rsaPrivateKey);
} catch (Exception e) {
return null;
}
}
private Map<String, String[]> buildParams(String src) throws UnsupportedEncodingException {
Map<String, String[]> map = new HashMap<>();
Map<String, String> params = JSONObject.parseObject(src, new TypeReference<Map<String, String>>() {
});
for (String key : params.keySet()) {
map.put(key, new String[] { params.get(key) });
}
return map;
}
@Override
public String getParameter(String name) {
String[] values = getParameterMap().get(name);
if (values != null) {
return (values.length > 0 ? values[0] : null);
}
return super.getParameter(name);
}
@Override
public String[] getParameterValues(String name) {
String[] values = getParameterMap().get(name);
if (values != null) {
return values;
}
return super.getParameterValues(name);
}
@Override
public Enumeration<String> getParameterNames() {
Map<String, String[]> multipartParameters = getParameterMap();
if (multipartParameters.isEmpty()) {
return super.getParameterNames();
}
Set<String> paramNames = new LinkedHashSet<String>();
Enumeration<String> paramEnum = super.getParameterNames();
while (paramEnum.hasMoreElements()) {
paramNames.add(paramEnum.nextElement());
}
paramNames.addAll(multipartParameters.keySet());
return Collections.enumeration(paramNames);
}
@Override
public Map<String, String[]> getParameterMap() {
return null == parameterMap ? super.getParameterMap() : parameterMap;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return this.inputStream == null ? super.getInputStream() : (ServletInputStream) this.inputStream;
}
}
/**
* Пользовательский ServletInputStream
*
* @author ldy
*
*/
private class DecryptInputStream extends ServletInputStream {
private final InputStream sourceStream;
/**
* Create a DelegatingServletInputStream for the given source stream.
*
* @param sourceStream
* the source stream (never {@code null})
*/
public DecryptInputStream(InputStream sourceStream) {
Assert.notNull(sourceStream, "Source InputStream must not be null");
this.sourceStream = sourceStream;
}
@Override
public int read() throws IOException {
return this.sourceStream.read();
}
@Override
public void close() throws IOException {
super.close();
this.sourceStream.close();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
}
package com.itvip666.apisafe.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
/**
* Description: Сохраните поток внутри фильтра
* BodyReaderHttpServletRequestWrapper добрый Основная функция – копировать. HttpServletRequest входной поток,
* Иначе ты это вынесешь body После проверки параметров переходим в Controller Когда параметры приема будут null
* @author shenguangyang
* @date 2021/01/22
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private static final Logger log = LoggerFactory.getLogger(BodyReaderHttpServletRequestWrapper.class);
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
/**
* Получить тело запроса
*
* @param request
* @return
*/
public String getBodyString(final ServletRequest request) {
StringBuilder sb = new StringBuilder();
try (
InputStream inputStream = cloneInputStream(request.getInputStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* Description: копироватьвходной поток</br>
*
* @param inputStream
* @return</br>
*/
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
CacheKeyPrefix
/**
* Description: Префикс для хранения ключа кэша
* @author shenguangyang
* @date 2021/01/22
*/
public interface CacheKeyPrefix {
String SIGN_KEY_PRE_KEY = "sign:key:";
}
User
/**
* Description: Пользовательская таблица
* @author shenguangyang
* @date 2021/01/20
*/
@Data
public class User {
private String id;
private String username;
private String password;
private String token;
}
/**
* Description: Служба кэширования
* @author shenguangyang
* @date 2021/01/20
*/
public interface CacheService {
/**
* держать
* @param key ключ
* @param value ценить
* @param timeout тайм-аут,Единица измерения: с.
*/
void save(Object key , Object value , long timeout);
/**
* Отложить ключ. Если срок действия ключа истекает, Воля сбрасывает время истечения срока действия на. timeout s
* @param key ключ
* @param value ценить
* @param timeout Срок годности Единица измерения: с.
*/
void delay(Object key , Object value , long timeout);
/**
* Находить
* @param key
* @return
*/
Object find(Object key);
/***
* Определить, существует ли определенный ключ
*/
Boolean hasKey(Object key);
}
/**
* Description: Служба кэширования
* @author shenguangyang
* @date 2021/01/20
*/
@Service
public class CacheServiceImpl implements CacheService {
@Resource
RedisTemplate myRedisTemplate;
@Override
public void save(Object key, Object value, long timeout) {
myRedisTemplate.opsForValue().set(key,value,timeout, TimeUnit.SECONDS);
}
@Override
public void delay(Object key, Object value, long timeout) {
if (myRedisTemplate.opsForValue().getOperations().getExpire(key) != -2
&& myRedisTemplate.opsForValue().getOperations().getExpire(key) < 20){
myRedisTemplate.opsForValue().set(key,value,timeout, TimeUnit.SECONDS);
}
}
@Override
public Object find(Object key) {
Object o = myRedisTemplate.opsForValue().get(key);
return o;
}
@Override
public Boolean hasKey(Object key) {
Boolean aBoolean = myRedisTemplate.hasKey(key);
return aBoolean;
}
}
/**
* Description: RSA-сервис
* @author shenguangyang
* @date 2021/01/22
*/
public interface RSAService {
/**
* держатьApiзнакkeyприезжать缓存середина,по умолчанию Срок годностидля30s
* @return Вернуть ключ в кэш
*/
String saveApiSignKeyToCache(String key);
}
/**
* Description: Служба асимметричного шифрования RSA
* @author shenguangyang
* @date 2021/01/22
*/
@Service
public class RSAServiceImpl implements RSAService {
@Resource
CacheService cacheService;
@Override
public String saveApiSignKeyToCache(String key) {
String cache = UUID.randomUUID().toString().replace("-","");
cacheService.save(CacheKeyPrefix.SIGN_KEY_PRE_KEY + cache,key,30);
return cache;
}
}
/**
* Description: жетонзнак сервисный уровень
* @author shenguangyang
* @date 2021/01/20
*/
public interface TokenSignService {
/**
* Вход пользователя
* @param user пользовательский объект
* @return token
*/
String login(User user);
/**
* Пользователь по идентификатору пользователя
* @param uid
* @return
*/
User findUser(String uid);
}
/**
* Description:
* @author shenguangyang
* @date 2021/01/20
*/
@Service
public class TokenSignServiceImpl implements TokenSignService {
@Resource
CacheService cacheService;
@Override
public String login(User user) {
String username = user.getUsername();
String password = user.getPassword();
if (username.equals("test") && password.equals("test")) {
String token = UUID.randomUUID().toString().replace("-","");
cacheService.save("user:token:" + token , user , 10 * 60);
return token;
} else {
throw new BusinessException(ResultEnum.USERNAME_PASSOWRD_ERROR);
}
}
@Override
public User findUser(String uid) {
return null;
}
}
ApiUtils
package com.itvip666.apisafe.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.itvip666.result.Result;
import org.springframework.http.HttpMethod;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Method;
import java.util.*;
/**
* Description: Apiинструментдобрый
* @author shenguangyang
* @date 2021/01/21
*/
public class ApiUtils {
/**
* результат ответа
* @param response
* @param code Код ошибки
* @param msg сообщение об ошибке
* @throws Exception
*/
public static void result(HttpServletResponse response , int code , String msg) throws Exception {
response.setContentType("application/json; charset=UTF-8");
Result<Object> result = Result.failure(code, msg);
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(result));
}
/**
* Параметры ВоляURL объединяются с параметрами тела. Если это запрос на получение, нет необходимости получать параметры тела, требуются только параметры URL.
* @author show
* @date 14:24 2019/5/29
* @param request
*/
public static Map<String, String> getAllParams(HttpServletRequest request) throws IOException {
Map<String, String> result = new HashMap<>();
//Получаем параметры по URL
Map<String, String> urlParams = getUrlParams(request);
for (Map.Entry entry : urlParams.entrySet()) {
result.put((String) entry.getKey(), (String) entry.getValue());
}
Map<String, String> allRequestParam = new HashMap<>(16);
// Запрос на получение не обязательно должен принимать параметр body.
if (!HttpMethod.GET.name().equals(request.getMethod())) {
allRequestParam = getAllRequestParam(request);
}
//Объединяем параметры ВоляURL и параметры тела
if (allRequestParam != null) {
for (Map.Entry entry : allRequestParam.entrySet()) {
result.put((String) entry.getKey(), (String) entry.getValue());
}
}
return result;
}
/**
* получать Body параметр
* @author show
* @date 15:04 2019/5/30
* @param request
*/
public static Map<String, String> getAllRequestParam(final HttpServletRequest request) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String str = "";
StringBuilder wholeStr = new StringBuilder();
//Читаем содержимое тела построчно;
while ((str = reader.readLine()) != null) {
wholeStr.append(str);
}
//Преобразуем в объект json
return !(wholeStr.length() == 0) ? JSONObject.parseObject(wholeStr.toString(), Map.class) : new HashMap<>();
}
/**
* ВоляURLпроситьпараметр Конвертировать成Map
* @author show
* @param request
*/
public static Map<String, String> getUrlParams(HttpServletRequest request) {
Map<String, String> params = new HashMap<String, String>();
Enumeration<?> pNames = request.getParameterNames();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
String pValue = (String)request.getParameter(pName);
params.put(pName, pValue);
}
return params;
}
}
MD5Utils
package com.itvip666.apisafe.util;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Description: MD5шифрование/проверятьинструментдобрый
* @author shenguangyang
* @date 2021/01/20
*/
public class MD5Utils {
static final char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
static final char hexDigitsLower[] = { '0', '1', '2', '3', '4', '5', '6', '7','8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
/**
* вернонить MD5 никто Сольценитьшифрование
*
* @param plainText
* Входящий запросшифрованиеизнить * @return
* MD5шифрование генерирует 32 цифры (строчные буквы + цифры) нить
*/
public static String MD5Lower(String plainText) {
try {
// Получить алгоритм дайджеста MD5 MessageDigest объект
MessageDigest md = MessageDigest.getInstance("MD5");
// Обновить дайджест, используя указанные байты
md.update(plainText.getBytes());
// дайджест() наконец возвращает md5 hashценить,возвращатьсяценитьдля8Кусочекнить。因дляmd5 hashценитьда16Кусочекизhexценить,实际начальство就да8Кусочекиз字符
// BigIntegerфункциональное правило Воля8Кусочекизнить Конвертировать成16Кусочекhexценить,использоватьнитьвыражать;得приезжатьнить形式изhashценить。1 зафиксированныйценить
return new BigInteger(1, md.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
/**
* вернонить MD5 шифрование
*
* @param plainText
* Входящий запросшифрованиеизнить * @return
* MD5шифрование генерирует 32 цифры (заглавные буквы + цифры) нить
*/
public static String MD5Upper(String plainText) {
try {
// Получить алгоритм дайджеста MD5 MessageDigest объект
MessageDigest md = MessageDigest.getInstance("MD5");
// Обновить дайджест, используя указанные байты
md.update(plainText.getBytes());
// Получить зашифрованный текст
byte[] mdResult = md.digest();
// Преобразовать зашифрованный текст Пучок в шестнадцатеричную форму
int j = mdResult.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = mdResult[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];// Получить старший байт 4 цифровое преобразование битов, >>> Это логический сдвиг вправо, и бит знака Воли вместе смещается вправо.
str[k++] = hexDigits[byte0 & 0xf]; // Получить младший байт 4 преобразование битового числа
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* вернонить MD5 добавлять Сольценитьшифрование
*
* @param plainText
* Входящий запросшифрованиеизнить * @param saltValue
* Входящий запросдобавлятьиз Сольценить
* @return
* MD5шифрование генерирует 32 цифры (строчные буквы + цифры) нить
*/
public static String MD5Lower(String plainText, String saltValue) {
try {
// Получить алгоритм дайджеста MD5 MessageDigest объект
MessageDigest md = MessageDigest.getInstance("MD5");
// Обновить дайджест, используя указанные байты
md.update(plainText.getBytes());
md.update(saltValue.getBytes());
// дайджест() наконец возвращает md5 hashценить,возвращатьсяценитьдля8Кусочекнить。因дляmd5 hashценитьда16Кусочекизhexценить,实际начальство就да8Кусочекиз字符
// BigIntegerфункциональное правило Воля8Кусочекизнить Конвертировать成16Кусочекhexценить,использоватьнитьвыражать;得приезжатьнить形式изhashценить。1 зафиксированныйценить
return new BigInteger(1, md.digest()).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
/**
* вернонить MD5 добавлять Сольценитьшифрование
*
* @param plainText
* Входящий запросшифрованиеизнить * @param saltValue
* Входящий запросдобавлятьиз Сольценить
* @return
* MD5шифрование генерирует 32 цифры (заглавные буквы + цифры) нить
*/
public static String MD5Upper(String plainText, String saltValue) {
try {
// Получить алгоритм дайджеста MD5 MessageDigest объект
MessageDigest md = MessageDigest.getInstance("MD5");
// Обновить дайджест, используя указанные байты
md.update(plainText.getBytes());
md.update(saltValue.getBytes());
// Получить зашифрованный текст
byte[] mdResult = md.digest();
// Преобразовать зашифрованный текст Пучок в шестнадцатеричную форму
int j = mdResult.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = mdResult[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* MD5шифрование генерирует 32 цифры (строчные буквы + цифры) нить
* такой же MD5Lower() Такой же
*/
public final static String MD5(String plainText) {
try {
MessageDigest mdTemp = MessageDigest.getInstance("MD5");
mdTemp.update(plainText.getBytes("UTF-8"));
byte[] md = mdTemp.digest();
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigitsLower[byte0 >>> 4 & 0xf];
str[k++] = hexDigitsLower[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
/**
* Проверьте код MD5
*
* @param text
* нет необходимости проверять
* @param md5
* md5ценить
* @return Результат проверки
*/
public static boolean valid(String text, String md5) {
return md5.equals(MD5(text)) || md5.equals(MD5(text).toUpperCase());
}
/**
* тест
* @param args
*/
public static void main(String[] args) {
String plainText = "admin";
String saltValue = "admin123";
System.out.println(MD5Lower(plainText));
System.out.println(MD5Upper(plainText));
System.out.println(MD5Lower(plainText, saltValue));
System.out.println(MD5Upper(plainText, saltValue));
System.out.println(MD5(plainText));
System.out.println("=====Результат проверки======");
System.out.println(valid(plainText,MD5(plainText)));
}
}
RSAUtils
package com.itvip666.apisafe.util;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* Description: RSAшифрованиеинструментдобрый
* @author shenguangyang
* @date 2021/01/20
*/
public class RSAUtils {
/**
* Длина ключа Соответствует длине исходного текста. И чем дольше, тем медленнее
*/
private final static int KEY_SIZE = 1024;
/**
* Используется для инкапсуляции случайно сгенерированных открытых и закрытых ключей.
*/
private static Map<Integer, String> keyMap = new HashMap<Integer, String>();
/**
* Случайно сгенерировать пару ключей
*/
public static void genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGeneratorдобрыйиспользуется длягенерироватьоткрытый ключизакрытый ключверно,на основеRSAгенерация алгоритмаобъект
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// Инициализировать генератор пары ключей
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
// генерировать一个密钥верно,держатьсуществоватьkeyPairсередина
KeyPair keyPair = keyPairGen.generateKeyPair();
// получить закрытый ключ
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// получить открытый ключ
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
// получить закрытый ключнить
String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
// Воляоткрытый ключизакрытый ключдержатьприезжатьMap
//0 представляет открытый ключ
keyMap.put(0, publicKeyString);
//1 представляет закрытый ключ
keyMap.put(1, privateKeyString);
}
/**
* RSAоткрытый ключшифрование
*
* @param str шифрованиенить
* @param publicKey открытый ключ
* @return зашифрованный текст
* @throws Exception Информация об исключениях в процессе шифрования
*/
public static String encrypt(String str, String publicKey) throws Exception {
//base64编码изоткрытый ключ
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSAшифрование
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSAзакрытый ключ Расшифровать
*
* @param str шифрованиенить
* @param privateKey закрытый ключ
* @return простой текст
* @throws Exception Расшифровать过程серединаиз异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception {
//64-битное декодирование нить
byte[] inputByte = Base64.getDecoder().decode(str);
//base64编码иззакрытый ключ
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSAРасшифровать
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
public static void main(String[] args) throws Exception {
long temp = System.currentTimeMillis();
//генерироватьоткрытый ключизакрытый ключ
genKeyPair();
//шифрованиенить
System.out.println("открытый ключ:" + keyMap.get(0));
System.out.println("закрытый ключ:" + keyMap.get(1));
System.out.println("Время, затраченное на генерацию ключа:" + (System.currentTimeMillis() - temp) / 1000.0 + "Второй");
//идентификатор клиента + Время авторизации + Используемые модули
String message = "4028138151b3cf300151b419df0904028138151b3cf300151b419df0900074028138151b3cf300151b419df0900074028138151b3cf300151b419df090007007" + "2015-12-17 11:30:22" + "A01,A02";
System.out.println("Исходный текст:" + message);
System.out.println("После оригинального шифрования MD5 : " + (message = MD5Utils.MD5(message)));
temp = System.currentTimeMillis();
//через исходный текст,иоткрытый ключшифрование。
String messageEn = encrypt(message, keyMap.get(0));
System.out.println("зашифрованный текст:" + messageEn);
System.out.println("Затраченное время на шифрование:" + (System.currentTimeMillis() - temp) / 1000.0 + "Второй");
temp = System.currentTimeMillis();
//проходитьзашифрованный текст,изакрытый ключ Расшифровать。
String messageDe = decrypt(messageEn, keyMap.get(1) + "21");
System.out.println("Расшифровать:" + messageDe);
System.out.println("Расшифровать затраченное время:" + (System.currentTimeMillis() - temp) / 1000.0 + "Второй");
String message1 = "402813818151b3cf300151b419df090007007" + "2015-12-17 11:30:22" + "A01,A02";
System.out.println("message1: " + message1);
System.out.println("После оригинального шифрования MD5 : " + (message1 = MD5Utils.MD5(message1)));
String messageEn1 = encrypt(message1, keyMap.get(0));
System.out.println("зашифрованный текст:" + messageEn1);
String messageDe1 = decrypt(messageEn1, keyMap.get(1));
System.out.println("Расшифровать:" + messageDe1);
}
}
SignUtils
package com.itvip666.apisafe.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class SignUtils {
private static final Logger log = LoggerFactory.getLogger(SignUtils.class);
private static final String secretKeyOfWxh = "e10adc3949ba59abbe56e057f20f883f";
private static final String APPLICATION_JSON = "application/json";
public static void main(String[] args) throws Exception {
// алгоритм параметрического тестирования
HashMap<String, String> signMap = new HashMap<String, String>();
String ts = String.valueOf(getTimestamp());
String token = "";
String nonce = "";
signMap.put("token","daca676bb80e46a5a36e6abe85b1a86d");
signMap.put("nonce","123456");
signMap.put("uid","2");
signMap.put("ts",ts);
String verifySign = getSign(signMap, ts , token , nonce , secretKeyOfWxh);
System.out.println("得приезжатьзнакsign1:"+verifySign);
System.out.println(ts);
}
/**
* проверятьзнакда否合法
* @param params 访问из全部параметр
* @param request просить
* @param sign 客户端传过来иззнак
* @param secretKeyOfWxh Секретный ключ, используемый шифрованием
* @return
*/
public static Boolean checkSign(Map<String,String> params ,
HttpServletRequest request ,
String sign , String secretKeyOfWxh) throws Exception {
Boolean flag= false;
String token = request.getHeader("token");
String ts = request.getHeader("ts");
// случайное число,внешний интерфейс Основные гарантии генерации случайных чиселзнакиз多变性
String nonce = request.getHeader("nonce");
String verifySign = getSign(params, ts , token , nonce , secretKeyOfWxh);
log.debug("现существоватьизsign-->>" + sign);
log.debug("проверятьизsign-->>" + verifySign);
if(sign.equals(verifySign)){
flag = true;
}
return flag;
}
/**
* 得приезжатьзнак
* Данные формата подписи (по возрастанию словаря) + токен + ключ (случайно сгенерированный секретный ключ) + ts (текущая временная метка) + nonce (случайное число)
* @param params Коллекция параметров не содержит секретного ключа
* @param secret Проверьте секретный ключ интерфейса
* @param token жетон
* @param nonce случайныйнить * @param ts Временная метка
* @return
*/
public static String getSign(Map<String, String> params, String ts ,
String token , String nonce , String secret) throws IOException {
String sign="";
StringBuilder sb = new StringBuilder();
//step1:先вернопроситьпараметрсортировать
Set<String> keyset = params.keySet();
TreeSet<String> sortSet = new TreeSet<String>();
sortSet.addAll(keyset);
Iterator<String> it = sortSet.iterator();
//step2:Пучокпараметризkey значение привязано Секретный ключ помещается в конце, чтобы получить желаемое шифрование.
while(it.hasNext()) {
String key=it.next();
String value=params.get(key);
sb.append(key).append(value);
}
sb.append(token).append(secret).append(ts).append(nonce);
byte[] md5Digest;
// getMd5шифрованиеполучить знак
md5Digest = getMD5Digest(sb.toString());
sign = byte2hex(md5Digest);
return sign;
}
public static long getTimestamp(){
long timestampLong = System.currentTimeMillis();
long timestampsStr = timestampLong / 1000;
return timestampsStr;
}
public static String utf8Encoding(String value, String sourceCharsetName) {
try {
return new String(value.getBytes(sourceCharsetName), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] getMD5Digest(String data) throws IOException {
byte[] bytes = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
bytes = md.digest(data.getBytes("UTF-8"));
} catch (GeneralSecurityException gse) {
throw new IOException(gse);
}
return bytes;
}
private static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
}
/**
* apiинтерфейс Безопасность
* 所有изпросить必须携带к下параметр
* 1. ts: Временная метка
* 2. sign: знак (Необходимый,Если не использовать знак,Вы можете заполнить его случайно,Просто не может быть пусто)
* 3. token: жетон
* 4. nonce: случайныйнить * @author shenguangyang
*/
@SpringBootApplication
@ServletComponentScan("com.itvip666.apisafe.filter")
public class ApiSafeMain {
public static void main(String[] args) {
SpringApplication.run(ApiSafeMain.class,args);
}
}
/**
* axios.js предоставляет пакет запросапросить
* включать получить, опубликовать, удалить, поставить и т. д.
* @author: sgy
*/
import axios from 'axios';
import store from '@/store';
import router from '@/router'
import {aes, sign} from '@/common/js/crypto';
import {RSAencrypt} from "@/common/js/encrypt"
import { Message } from 'element-ui';
const ajax = axios.create({
// baseURL: store.getters.serviceHost, // префикс URL
baseURL: 'http://127.0.0.1:9090',
timeout: 10000, // Тайм-аут в миллисекундах
withCredentials: true // Перенос файлов cookie с информацией аутентификации
});
/**
* getСпособпросить,передача параметра URL
* @param url проситьurl
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
*/
const get = (url, params, level) => ajax(getConfig(url, 'get', true, params, level)).then(res => successback(res)).catch(error => errback(error));
/**
* postСпособпросить Передача параметров в режиме json
* @param url проситьurl
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
*/
const postJson = (url, params, level) => ajax(getConfig(url, 'post', true, params, level)).then(res => successback(res)).catch(error => errback(error));
/**
* postСпособпросить Передача параметров формы
* @param url проситьurl
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
*/
const post = (url, params, level) => ajax(getConfig(url, 'post', false, params, level)).then(res => successback(res)).catch(error => errback(error));
/**
* deleteСпособпросить передача параметра URL
* @param url проситьurl
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
*/
const del = (url, params, level) => ajax(getConfig(url, 'delete', true, params, level)).then(res => successback(res)).catch(error => errback(error));
/**
* putСпособпросить передача параметров json
* @param url проситьurl
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
*/
const putJson = (url, params, level) => ajax(getConfig(url, 'put', true, params, level)).then(res => successback(res)).catch(error => errback(error));
/**
* putСпособпросить Передача параметров формы
* @param url проситьurl
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
*/
const put = (url, params, level) => ajax(getConfig(url, 'put', false, params, level)).then(res => successback(res)).catch(error => errback(error));
// параметр Конвертировать
const param2String = data => {
console.log('data', data);
if (typeof data === 'string') {
return data;
}
let ret = '';
for (let it in data) {
let val = data[it];
if (typeof val === 'object' && //
(!(val instanceof Array) || (val.length > 0 && (typeof val[0] === 'object')))) {
val = JSON.stringify(val);
}
ret += it + '=' + encodeURIComponent(val) + '&';
}
if (ret.length > 0) {
ret = ret.substring(0, ret.length - 1);
}
return ret;
};
/**
* Создать случайную нить
* @param len Укажите длину
*/
const randomString = len => {
len = len || 32;
let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; /****Символы oOLl,9gq,Vv,Uu,I1, которые легко спутать****/
let maxPos = $chars.length;
let pwd = '';
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
// функция обратного вызова ошибки
const errback = error => {
if ('code' in error) {
// Не авторизован
if (error.code === 1005) {
sessionStorage.clear();
router.push({path: '/login',query: {redirect: router.currentRoute.fullPath}})
return {};
}
return Promise.reject(error);
}
// Неисправность сети или тайм-аут соединения
Message({
message: error.message,
type: 'error'
});
return Promise.reject({data: error.message});
};
// функция обратного вызова при успешном выполнении
const successback = res => {
if (res.status === 200 && res.data.code !== 200) {
Message({
message: res.data.msg,
type: 'error'
});
return Promise.reject(res.data);
}
return res.data;
};
/**
* Количество миллисекунд, начиная с 1970 года и затем усеченное до 10 цифр, становится Количество секунд с 1970 года
* @return Вернуть 10 цифр Временная метка
*/
const timestamp = () => {
// new Date().getTime();
let tmp = Date.parse( new Date() ).toString();
tmp = tmp.substr(0,10);
return tmp;
}
/**
* @param url проситьurl
* @param method get,post,put,delete
* @param isjson да否jsonпредставлять на рассмотрениепараметр
* @param params параметр
* @param level 0:никтошифрование,1:параметршифрование,2: знак+Временная метка; По умолчанию 0
* Данные формата подписи (по возрастанию словаря) + токен + ключ (случайно сгенерированный секретный ключ) + ts (текущая временная метка) + nonce (случайное число)
*/
const getConfig = (url, method, isjson, params, level = 0) => {
let config_ = {
url,
method,
// params, data,
headers: {
level
}
};
// получатьtoken
let token = store.state.token;
if (!token) {
let user = sessionStorage.getItem('user')
token = user == null ? '' : JSON.parse(user).token;
store.state.token = token;
}
// Временная метка
if (level === 1) { // шифрование
params = {encrypt: RSAencrypt(JSON.stringify(params))};
config_.headers = {
level,
token,
};
} else if (level === 2) { // знак
let ts = timestamp()
// знакkey
let key = randomString(16);
// шифрованиепозжезнакkey
let keyEncrypt = RSAencrypt(key)
// случайныйнить let nonce = randomString(10)
// знак串
let signstr = sign(token, ts , params , key , nonce);
console.log("params",params);
console.log('token', token);
console.log('sign', signstr);
console.log('nonce',nonce);
console.log('ts',ts);
config_.headers = {
level,
ts,
sign: signstr,
token,
nonce,
key: keyEncrypt
};
}
// 表单представлять на рассмотрениепараметр
if (!isjson) {
config_.headers['Content-Type'] = 'application/x-www-form-urlencoded';
config_.responseType = 'text';
config_.transformRequest = [function (data) {
return param2String(data);
}];
}
// настраиватьпараметр
if (method in {'get': true, 'delete': true}) {
config_.params = params;
} else if (method in {'post': true, 'put': true}) {
config_.data = params;
}
return config_;
};
// Выходной порт унифицированного метода
export {
ajax,
get,
postJson,
post,
del,
putJson,
put
};
/**
* Реализовано через crypto-js добавлять Расшифроватьинструмент
* AES、HASH(MD5、SHA256)、base64
* @author: sgy
*/
import CryptoJS from 'crypto-js';
const KP = {
key: '1234567812345678', // секретный ключ 16*n:
iv: '1234567812345678' // компенсировать
};
function getAesString(data, key, iv) { // шифрование
key = CryptoJS.enc.Utf8.parse(key);
// alert(key);
iv = CryptoJS.enc.Utf8.parse(iv);
let encrypted = CryptoJS.AES.encrypt(data, key,
{
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString(); // возвращатьсяиздаbase64格式иззашифрованный текст
}
function getDAesString(encrypted, key, iv) { // Расшифровать
key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
let decrypted = CryptoJS.AES.decrypt(encrypted, key,
{
iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypted.toString(CryptoJS.enc.Utf8); //
}
// AES симметриясекретный ключшифрование
const aes = {
en: (data) => getAesString(data, KP.key, KP.iv),
de: (data) => getDAesString(data, KP.key, KP.iv)
};
// BASE64
const base64 = {
en: (data) => CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(data)),
de: (data) => CryptoJS.enc.Base64.parse(data).toString(CryptoJS.enc.Utf8)
};
// SHA256
const sha256 = (data) => {
return CryptoJS.SHA256(data).toString();
};
// MD5
const md5 = (data) => {
return CryptoJS.MD5(data).toString();
};
/**
* знак
* @param token Личность
* @param timestamp знак Временная метка
* @param data данные знака
* @param key знакkey
* @param nonce случайныйнить*/
const sign = (token, timestamp, data , key , nonce) => {
// Данные формата подписи (по возрастанию словаря) + токен + ключ (случайно сгенерированный секретный ключ) + ts (текущая временная метка) + nonce (случайное число)
let ret = [];
for (let it in data) {
let val = data[it];
if (typeof val === 'object' && //
(!(val instanceof Array) || (val.length > 0 && (typeof val[0] === 'object')))) {
val = JSON.stringify(val);
}
ret.push(it + val);
}
// словарь в порядке возрастания
ret.sort();
let signsrc = ret.join('') + token + key + timestamp + nonce;
return md5(signsrc).toUpperCase();
};
export {
aes,
md5,
sha256,
base64,
sign
};
import Vue from 'vue'
import JsEncrypt from 'jsencrypt'
Vue.prototype.$jsEncrypt = JsEncrypt
/**
* интерфейс Безопасность *
* 1. Установить
* jsencryptда一个rasшифрование Расшифровать Библиотека
* npm install jsencrypt --save
*
* 2. Представлено в main.js
* import JsEncrypt from 'jsencrypt'
* Vue.prototype.$jsEncrypt = JsEncrypt
*/
let publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCSDKCuvX2Sod4+FgDCDHSFZhmAODg5cnsQ6BZmCl+Zs3dXNNcU4DOTbCCvmT1dXDQyM9DKh6jog6q1hWAB1dUDa1VNiKZNZbZMZKReO1At4y0xh1EVRNFFnLSzzr1+JY1k6McuoiVfpfqRSn7Vw4IcpzJVc3T8jpRcqnstgMLKMQIDAQAB'
let privateKey = '这里да封装иззакрытый ключ'
//шифрованиеметод
export function RSAencrypt(pas) {
//Создаем экземпляр объекта jsEncrypt
let jse = new JSEncrypt();
//настраиватьоткрытый ключ
jse.setPublicKey(publicKey);
// console.log('шифрование:'+jse.encrypt(pas))
return jse.encrypt(pas);
}
//Расшифроватьметод
export function RSAdecrypt(pas) {
let jse = new JSEncrypt();
// закрытый ключ
jse.setPrivateKey(privateKey)
// console.log('Расшифровать:'+jse.decrypt(pas))
return jse.decrypt(pas);
}
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: () => import("@/views/Home.vue")
},
{
path: '/login',
name: 'login',
component: () => import("@/views/Login.vue")
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
Home.vue
<template>
<div>
<el-row>
<el-button>по умолчанию按钮</el-button>
<el-button type="primary" @click="open1">Находитьиспользовать户</el-button>
<el-button type="success" @click="open2">держатьиспользовать户</el-button>
<el-button type="info" @click="open3">Кнопка информации</el-button>
<el-button type="warning" @click="open4">кнопка предупреждения</el-button>
</el-row>
</div>
</template>
<script>
import {get,postJson} from "@/common/js/axios"
export default {
methods: {
open1() {
get("/token/find",{uid:2},2).then(res => {
if ('code' in res) {
console.log(res);
this.$message({
showClose: true,
message: 'Находитьиспользовать户'
});
}
});
},
open2() {
postJson("/token/save",{id: "",username:"test",password:"test"},1).then(res => {
if ('code' in res) {
console.log(res);
this.$message({
showClose: true,
message: 'держатьиспользовать户',
type: 'success'
});
}
});
},
open3() {
this.$message({
showClose: true,
message: «Внимание, это предупреждающее сообщение»,
type: 'warning'
});
},
open4() {
this.$message({
showClose: true,
message: '错Понятно哦,这да一条сообщение об ошибке',
type: 'error'
});
}
}
}
</script>
<style>
</style>
Login.vue
<template>
<div class="login">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="использовать户名">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="пароль">
<el-input v-model="form.password"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Авторизоваться</el-button>
<el-button>Отмена</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {postJson} from "@/common/js/axios"
export default {
data() {
return {
form: {
username: '',
password: '',
token: ''
}
}
},
methods: {
onSubmit() {
postJson("/token/login",this.form,0).then(res => {
console.log(res);
if ( 'code' in res ) {
// Воляобъект转дляjson
this.form.token = res.data
sessionStorage.setItem('user',JSON.stringify(this.form))
// Откуда идти?
if (this.$route.query.redirect) {
// Перейти на исходную страницу
this.$router.push({path: decodeURIComponent(this.$route.query.redirect)})
} else {
this.$router.push("/") //Страница, на которую открывается обычный процесс входа в систему
}
}
})
}
}
}
</script>
<style>
.login {
width: 400px;
}
</style>
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
import axios from "axios"
axios.defaults.baseURL = "http://127.0.0.1:8080"
axios.defaults.timeout = 5000
// Воляаксиос установлен в цепочке прототипов Vue.
Vue.prototype.$axios=axios
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')