技术标签: 加密解密 java jwt 中间件 entity
点击上方“芋道源码”,选择“设为星标”
管她前浪,还是后浪?
能浪的浪,才是好浪!
每天 10:33 更新文章,每天掉亿点点头发...
源码精品专栏
简述:当用户登录时,恶意攻击者可以用抓包工具可以拿到用户提交的表单信息,可以获取用户的账号密码,进而可以恶意访问网站。
RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
1973年,在英国政府通讯总部工作的数学家克利福德·柯克斯(Clifford Cocks)在一个内部文件中提出了一个相同的算法,但他的发现被列入机密,一直到1997年才被发表。对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。
假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就肯定会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式解破。到目前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。
1983年麻省理工学院在美国为RSA算法申请了专利。这个专利2000年9月21日失效。由于该算法在申请专利前就已经被发表了,在世界上大多数其它地区这个专利权不被承认。
非对称算法的在应用的过程如下:
接收方生成公钥和私钥,公钥公开,私钥保留;
发送方将要发送的消息采用公钥加密,得到密文,然后将密文发送给接收方;
接收方收到密文后,用自己的私钥进行解密,获得明文。
package com.demo.utils;
import java.util.Map;
@Slf4j
public class RSAUtils {
public static final String PUBLIC_KEY = "public_key";
public static final String PRIVATE_KEY = "private_key";
public static Map<String, String> generateRasKey() {
Map<String, String> rs = new HashMap<>();
try {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥 公钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
rs.put(PUBLIC_KEY, publicKeyString);
rs.put(PRIVATE_KEY, privateKeyString);
} catch (Exception e) {
log.error("RsaUtils invoke genKeyPair failed.", e);
throw new RsaException("RsaUtils invoke genKeyPair failed.");
}
return rs;
}
public static String encrypt(String str, String publicKey) {
try {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
log.error("RsaUtils invoke encrypt failed.", e);
throw new RsaException("RsaUtils invoke encrypt failed.");
}
}
public static String decrypt(String str, String privateKey) {
try {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
} catch (Exception e) {
log.error("RsaUtils invoke decrypt failed.", e);
throw new RsaException("RsaUtils invoke decrypt failed.");
}
}
}
RsaException: 是自定义异常
@Getter
public class RsaException extends RuntimeException {
private final String message;
public RsaException(String message) {
this.message = message;
}
}
package com.rosh;
public class RsaTest {
/**
* 用测试生成的公钥,私钥赋值
*/
private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB";
private static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIW1OUvrUiogZ359EtSEnQyOyoVcUmzAKiEnjmbnVa9vEM9E/eCWVbRYKGTvgPkkQ6kwNJajgLtF+gaUFE1buRDqpga6RhnmOBinOhPT6Cneif3p9BcTJAnKy/3GJM8h2ZJddVWPUcA4nDb1FvPEhUpRLPM9e8S1dFO0ILX7CQAlAgMBAAECgYBC4amtbiKFa/wY61tV7pfYRjzLhKi+OUlZmD3E/4Z+4KGZ7DrJ8qkgMtDR3HO5LAikQrare1HTW2d7juqw32ascu+uDObf4yrYNKin+ZDLUYvIDfLhThPxnZJwQ/trdtfxO3VM//XbwZacmwYbAsYW/3QPUXwwOPAgbC2oth8kqQJBANKLyXcdjZx4cwJVl7xNeC847su8y6bPpcBASsaQloCIPiNBIg1h76dpfEGIQBYWJWbBsxtHe/MhOmz7fNFDS2sCQQCiktYZR0dZNH4eNX329LoRuBiltpr9tf36rVOlKr1GSHkLYEHF2qtyXV2mdrY8ZWpvuo3qm1oSLaqmop2rN9avAkBHk85B+IIUF77BpGeZVJzvMOO9z8lMRHuNCE5jgvQnbinxwkrZUdovh+T+QlvHJnBApslFFOBGn51FP5oHamFRAkEAmwZmPsinkrrpoKjlqz6GyCrC5hKRDWoj/IyXfKKaxpCJTH3HeoIghvfdO8Vr1X/n1Q8SESt+4mLFngznSMQAZQJBAJx07bCFYbA2IocfFV5LTEYTIiUeKdue2NP2yWqZ/+tB5H7jNwQTJmX1mn0W/sZm4+nJM7SjfETpNZhH49+rV6U=";
/**
* 生成公钥私钥
*/
@Test
public void generateRsaKey() {
Map<String, String> map = RSAUtils.generateRasKey();
System.out.println("随机生成的公钥为:" + map.get(RSAUtils.PUBLIC_KEY));
System.out.println("随机生成的私钥为:" + map.get(RSAUtils.PRIVATE_KEY));
}
/**
* 加密: Yeidauky/iN1/whevov2+ntzXJKAp2AHfESu5ixnDqH5iB7ww+TcfqJpDfkPHfb12Y0sVXw0gBHNJ4inkh7l2/SJBze3pKQU/mg3oyDokTia3JZIs+e80/iJcSfN+yA1JaqY+eJPYiBiOGAF2S6x0ynvJg/Wj0fwp2Tq3PDzRMo=
*/
@Test
public void testEncrypt() {
JSONObject jsonObject = new JSONObject();
jsonObject.put("username", "rosh");
jsonObject.put("password", "123456");
String str = jsonObject.toJSONString();
String encrypt = RSAUtils.encrypt(str, PUBLIC_KEY);
System.out.println(encrypt);
}
@Test
public void testDecrypt() {
String decrypt = RSAUtils.decrypt("Yeidauky/iN1/whevov2+ntzXJKAp2AHfESu5ixnDqH5iB7ww+TcfqJpDfkPHfb12Y0sVXw0gBHNJ4inkh7l2/SJBze3pKQU/mg3oyDokTia3JZIs+e80/iJcSfN+yA1JaqY+eJPYiBiOGAF2S6x0ynvJg/Wj0fwp2Tq3PDzRMo=",
PRIVATE_KEY);
System.out.println(decrypt);
}
}
SpringCloud Gateway + SpringBoot + Nacos+redis
后端把公钥跟前端约定好:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>登录</h1>
<from id="from">
账号:<input id="username" type="text"/>
<br/>
密码:<input id="password" type="password"/>
<br/>
<input id="btn_login" type="button" value="登录"/>
</from>
<script src="js/jquery.min.js"></script>
<script src="js/jsencrypt.js"></script>
<script type="text/javascript">
var encrypt = new JSEncrypt();
encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB");
$("#btn_login").click(function () {
const username = $("#username").val();
const password = $("#password").val();
const form = {};
form.username = username;
form.password = password;
$.ajax({
url: "http://localhost:9000/api/user/login",
data: encrypt.encrypt(JSON.stringify(form)),
type: "POST",
dataType: "json",
contentType: "application/json;charset=utf-8",
success: function (data) {
console.log(data);
}
});
})
</script>
</body>
</html>
设定公钥、token,token是登录成功后返回的值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查询测试</title>
</head>
<body>
id:<input id="id_txt" type="text"/>
<input id="btn_search" type="button" value="查询"/>
<script src="js/jquery.min.js"></script>
<script src="js/jsencrypt.js"></script>
<script type="text/javascript">
var encrypt = new JSEncrypt();
encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB");
$("#btn_search").click(function () {
const id = $("#id_txt").val();
const param = "id=" + id + "&requestId=" + getUuid();
encrypt.encrypt(param);
const url = "http://localhost:9000/api/user/detail?param=" + encrypt.encrypt(param);
$.ajax({
url: url,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("token", "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIzYzE1ODczYS1iMGUxLTQyNzctYTRjOS1kYTMwNjdiYmE0NWIiLCJpYXQiOjE2MzUzMDYwMDAsInN1YiI6IntcInBhc3N3b3JkXCI6XCIxMjM0NTZcIixcInVzZXJJZFwiOjEsXCJ1c2VybmFtZVwiOlwiYWRtaW5cIn0iLCJleHAiOjE2MzU1NjUyMDB9.fIQi_cV2ZMszBVFV4GoIpGhCSENQKrDi8DsbArk7mGk");
},
type: "GET",
success: function (data) {
console.log(data);
}
});
});
function getUuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 32; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23];
var uuid = s.join("");
return uuid;
}
</script>
</body>
</html>
解密前端传来的参数并修改传参
package com.demo.gateway.config;
public class GatewayFilterConfig implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1 如果是登录不校验Token
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
if (!pathMatcher.match("/user/login", requestUrl)) {
String token = exchange.getRequest().getHeaders().getFirst(UserConstant.TOKEN);
Claims claim = TokenUtils.getClaim(token);
if (StringUtils.isBlank(token) || claim == null) {
return FilterUtils.invalidToken(exchange);
}
}
//2 修改请求参数,并获取请求参数
try {
updateRequestParam(exchange);
} catch (Exception e) {
return FilterUtils.invalidUrl(exchange);
}
//3 获取请求体,修改请求体
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
String encrypt = RSAUtils.decrypt(body, RSAConstant.PRIVATE_KEY);
return Mono.just(encrypt);
});
//创建BodyInserter修改请求体
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
//创建CachedBodyOutputMessage并且把请求param加入
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
}
/**
* 修改前端传的参数
*/
private void updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String query = uri.getQuery();
if (StringUtils.isNotBlank(query) && query.contains("param")) {
String[] split = query.split("=");
String param = RSAUtils.decrypt(split[1], RSAConstant.PRIVATE_KEY);
Field targetQuery = uri.getClass().getDeclaredField("query");
targetQuery.setAccessible(true);
targetQuery.set(uri, param);
}
}
@Override
public int getOrder() {
return 80;
}
}
public abstract class AbstractExceptionHandler {
protected JSONObject buildErrorMap(Throwable ex) {
JSONObject json = new JSONObject();
if (ex instanceof RSAException || ex instanceof IllegalArgumentException) {
json.put("code", HttpStatus.BAD_REQUEST.value());
if (StringUtils.isNotBlank(ex.getMessage())){
json.put("msg", ex.getMessage());
}else {
json.put("msg", "无效的请求");
}
} else {
json.put("code", HttpStatus.BAD_REQUEST.value());
json.put("msg", "未知错误联系管理员");
}
return json;
}
}
@Configuration
public class GatewayExceptionConfig {
@Primary
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
GatewayExceptionHandler gatewayExceptionHandler = new GatewayExceptionHandler();
gatewayExceptionHandler.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
gatewayExceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
gatewayExceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return gatewayExceptionHandler;
}
}
package com.demo.gateway.exception;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Slf4j
public class GatewayExceptionHandler extends AbstractExceptionHandler implements ErrorWebExceptionHandler {
private List<HttpMessageReader<?>> messageReaders = Collections.emptyList();
private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList();
private List<ViewResolver> viewResolvers = Collections.emptyList();
private ThreadLocal<JSONObject> exceptionHandlerResult = new ThreadLocal<>();
public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) {
Assert.notNull(messageReaders, "'messageReaders' must not be null");
this.messageReaders = messageReaders;
}
public void setViewResolvers(List<ViewResolver> viewResolvers) {
this.viewResolvers = viewResolvers;
}
public void setMessageWriters(List<HttpMessageWriter<?>> messageWriters) {
Assert.notNull(messageWriters, "'messageWriters' must not be null");
this.messageWriters = messageWriters;
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
JSONObject errorInfo = super.buildErrorMap(ex);
if (exchange.getResponse().isCommitted()) {
return Mono.error(ex);
}
exceptionHandlerResult.set(errorInfo);
ServerRequest newRequest = ServerRequest.create(exchange, this.messageReaders);
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse).route(newRequest)
.switchIfEmpty(Mono.error(ex))
.flatMap(handler -> handler.handle(newRequest))
.flatMap(response -> write(exchange, response));
}
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
Map<String, Object> result = exceptionHandlerResult.get();
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
private Mono<? extends Void> write(ServerWebExchange exchange,
ServerResponse response) {
exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());
return response.writeTo(exchange, new ResponseContext());
}
private class ResponseContext implements ServerResponse.Context {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return GatewayExceptionHandler.this.messageWriters;
}
@Override
public List<ViewResolver> viewResolvers() {
return GatewayExceptionHandler.this.viewResolvers;
}
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public String login(@RequestBody UserForm userForm) {
return userService.login(userForm);
}
@GetMapping("/detail")
public JSONObject detail(@RequestParam("id") Long id) {
return userService.detail(id);
}
}
@Service
public class UserService {
private static final String USERNAME = "admin";
private static final String PASSWORD = "123456";
private static final Long USER_ID = 1L;
/**
* 模拟 登录 username = admin, password =123456,user_id 1L 登录成功 返回token
*/
public String login(UserForm userForm) {
String username = userForm.getUsername();
String password = userForm.getPassword();
if (USERNAME.equals(username) && PASSWORD.equals(password)) {
JSONObject userInfo = new JSONObject();
userInfo.put("username", USERNAME);
userInfo.put("password", PASSWORD);
userInfo.put("userId", USER_ID);
return TokenUtils.createToken(userInfo.toJSONString());
}
return "账号密码不正确";
}
public JSONObject detail(Long id) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
jsonObject.put("name", "admin");
return jsonObject;
}
}
登录:返回token
查询:
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
为了增强URL安全性,前端在header中添加时间戳。
在header中添加时间戳
private Long getDateTimestamp(HttpHeaders httpHeaders) {
List<String> list = httpHeaders.get("timestamp");
if (CollectionUtils.isEmpty(list)) {
throw new IllegalArgumentException("拒绝服务");
}
long timestamp = Long.parseLong(list.get(0));
long currentTimeMillis = System.currentTimeMillis();
//有效时长为5分钟
if (currentTimeMillis - timestamp > 1000 * 60 * 5) {
throw new IllegalArgumentException("拒绝服务");
}
return timestamp;
}
基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
项目地址:https://github.com/YunaiV/onemall
确保URL唯一性,前端请求中增加UUID,后端存入redis,有效时长为5分钟,5分钟重复提交拒绝服务
private String getRequestId(HttpHeaders headers) {
List<String> list = headers.get("requestId");
if (CollectionUtils.isEmpty(list)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
String requestId = list.get(0);
//如果requestId存在redis中直接返回
String temp = redisTemplate.opsForValue().get(requestId);
if (StringUtils.isNotBlank(temp)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES);
return requestId;
}
最后一步,添加签名
前端增加签名
跟前端约定好,json数据按照ASCII升序排序。
登录页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>登录</h1>
<from id="from">
账号:<input id="username" type="text"/>
<br/>
密码:<input id="password" type="password"/>
<br/>
<input id="btn_login" type="button" value="登录"/>
</from>
<script src="js/jquery.min.js"></script>
<script src="js/jsencrypt.js"></script>
<script src="js/md5.min.js"></script>
<script type="text/javascript">
var encrypt = new JSEncrypt();
encrypt.setPublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFtTlL61IqIGd+fRLUhJ0MjsqFXFJswCohJ45m51WvbxDPRP3gllW0WChk74D5JEOpMDSWo4C7RfoGlBRNW7kQ6qYGukYZ5jgYpzoT0+gp3on96fQXEyQJysv9xiTPIdmSXXVVj1HAOJw29RbzxIVKUSzzPXvEtXRTtCC1+wkAJQIDAQAB");
$("#btn_login").click(function () {
//表单
const username = $("#username").val();
const password = $("#password").val();
const form = {};
form.username = username;
form.password = password;
//生成签名,也可以加盐
const timestamp = Date.parse(new Date());
const data = JSON.stringify(sort_ASCII(form));
const requestId = getUuid();
const sign = MD5(data + requestId + timestamp);
$.ajax({
url: "http://localhost:9000/api/user/login",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("timestamp", timestamp);
XMLHttpRequest.setRequestHeader("requestId", requestId);
XMLHttpRequest.setRequestHeader("sign", sign);
},
data: encrypt.encrypt(data),
type: "POST",
dataType: "json",
contentType: "application/json;charset=utf-8",
success: function (data) {
console.log(data);
}
});
});
function getUuid() {
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 32; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23];
var uuid = s.join("");
return uuid;
}
function sort_ASCII(obj) {
var arr = new Array();
var num = 0;
for (var i in obj) {
arr[num] = i;
num++;
}
var sortArr = arr.sort();
var sortObj = {};
for (var i in sortArr) {
sortObj[sortArr[i]] = obj[sortArr[i]];
}
return sortObj;
}
</script>
</body>
</html>
/**
* @Description:
* @Author: Rosh
* @Date: 2021/10/27 11:03
*/
public class MyCachedBodyOutputMessage extends CachedBodyOutputMessage {
private Map<String, Object> paramMap;
private Long dateTimestamp;
private String requestId;
private String sign;
public MyCachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
super(exchange, httpHeaders);
}
public void initial(Map<String, Object> paramMap, String requestId, String sign, Long dateTimestamp) {
this.paramMap = paramMap;
this.requestId = requestId;
this.sign = sign;
this.dateTimestamp = dateTimestamp;
}
public Map<String, Object> getParamMap() {
return paramMap;
}
public Long getDateTimestamp() {
return dateTimestamp;
}
public String getRequestId() {
return requestId;
}
public String getSign() {
return sign;
}
}
package com.demo.gateway.config;
public class GatewayFilterConfig implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String ERROR_MESSAGE = "拒绝服务";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1 获取时间戳
Long dateTimestamp = getDateTimestamp(exchange.getRequest().getHeaders());
//2 获取RequestId
String requestId = getRequestId(exchange.getRequest().getHeaders());
//3 获取签名
String sign = getSign(exchange.getRequest().getHeaders());
//4 如果是登录不校验Token
String requestUrl = exchange.getRequest().getPath().value();
AntPathMatcher pathMatcher = new AntPathMatcher();
if (!pathMatcher.match("/user/login", requestUrl)) {
String token = exchange.getRequest().getHeaders().getFirst(UserConstant.TOKEN);
Claims claim = TokenUtils.getClaim(token);
if (StringUtils.isBlank(token) || claim == null) {
return FilterUtils.invalidToken(exchange);
}
}
//5 修改请求参数,并获取请求参数
Map<String, Object> paramMap;
try {
paramMap = updateRequestParam(exchange);
} catch (Exception e) {
return FilterUtils.invalidUrl(exchange);
}
//6 获取请求体,修改请求体
ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
String encrypt = RSAUtils.decrypt(body, RSAConstant.PRIVATE_KEY);
JSONObject jsonObject = JSON.parseObject(encrypt);
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
paramMap.put(entry.getKey(), entry.getValue());
}
checkSign(sign, dateTimestamp, requestId, paramMap);
return Mono.just(encrypt);
});
//创建BodyInserter修改请求体
BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
//创建CachedBodyOutputMessage并且把请求param加入,初始化校验信息
MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);
outputMessage.initial(paramMap, requestId, sign, dateTimestamp);
return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
Flux<DataBuffer> body = outputMessage.getBody();
if (body.equals(Flux.empty())) {
//验证签名
checkSign(outputMessage.getSign(), outputMessage.getDateTimestamp(), outputMessage.getRequestId(), outputMessage.getParamMap());
}
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
}
public void checkSign(String sign, Long dateTimestamp, String requestId, Map<String, Object> paramMap) {
String str = JSON.toJSONString(paramMap) + requestId + dateTimestamp;
String tempSign = Md5Utils.getMD5(str.getBytes());
if (!tempSign.equals(sign)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
}
/**
* 修改前端传的参数
*/
private Map<String, Object> updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String query = uri.getQuery();
if (StringUtils.isNotBlank(query) && query.contains("param")) {
String[] split = query.split("=");
String param = RSAUtils.decrypt(split[1], RSAConstant.PRIVATE_KEY);
Field targetQuery = uri.getClass().getDeclaredField("query");
targetQuery.setAccessible(true);
targetQuery.set(uri, param);
return getParamMap(param);
}
return new TreeMap<>();
}
private Map<String, Object> getParamMap(String param) {
Map<String, Object> map = new TreeMap<>();
String[] split = param.split("&");
for (String str : split) {
String[] params = str.split("=");
map.put(params[0], params[1]);
}
return map;
}
private String getSign(HttpHeaders headers) {
List<String> list = headers.get("sign");
if (CollectionUtils.isEmpty(list)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
return list.get(0);
}
private Long getDateTimestamp(HttpHeaders httpHeaders) {
List<String> list = httpHeaders.get("timestamp");
if (CollectionUtils.isEmpty(list)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
long timestamp = Long.parseLong(list.get(0));
long currentTimeMillis = System.currentTimeMillis();
//有效时长为5分钟
if (currentTimeMillis - timestamp > 1000 * 60 * 5) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
return timestamp;
}
private String getRequestId(HttpHeaders headers) {
List<String> list = headers.get("requestId");
if (CollectionUtils.isEmpty(list)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
String requestId = list.get(0);
//如果requestId存在redis中直接返回
String temp = redisTemplate.opsForValue().get(requestId);
if (StringUtils.isNotBlank(temp)) {
throw new IllegalArgumentException(ERROR_MESSAGE);
}
redisTemplate.opsForValue().set(requestId, requestId, 5, TimeUnit.MINUTES);
return requestId;
}
@Override
public int getOrder() {
return 80;
}
}
发现验签成功
验签成功
https://gitee.com/zhurongsheng/springcloud-gateway-rsa
欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:
已在知识星球更新源码解析如下:
最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。
提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。
获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。
文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法