SpringBoot请求拦截及请求参数加解密
代码已上传⾄github,如遇到问题,可参照代码
1)加密实现
后台代码实现:CodecUtil
这⾥我⽣成两个AES的私钥,⼀个只是提⾼SHA1加密的复杂度(这个可以不要,或者可以说任意的,类似于盐),另⼀个才是⽤于AES的加解密
/** AES密钥长度,⽀持128、192、256 */
private static final int AES_SECRET_KEY_LENGTH =128;
private static String generateAESSecretKeyBase64(String key){
try{
KeyGenerator keyGenerator = Instance("AES");
keyGenerator.init(AES_SECRET_KEY_LENGTH);
SecretKey secretKey = ateKey();
Encoded());
}catch(Exception e){
e.printStackTrace();
}
return null;
}
/** AES加密密钥 */
public static final byte[] AES_SECRET_KEY_BYTES = Base64Utils.decodeFromString("XjjkaLnlzAFbR399IP4kdQ==");
/** SHA1加密密钥(⽤于增加加密的复杂度) */
public static final String SHA1_SECRET_KEY ="QGZUanpSaSy9DEPQFVULJQ==";
使⽤AES实现加密解密
public static String aesEncrypt(String data){
try{
Cipher cipher = Instance("AES/ECB/PKCS5Padding");// 加密算法/⼯作模式/填充⽅式
byte[] dataBytes = Bytes();
cipher.init(Cipher.ENCRYPT_MODE,new SecretKeySpec(AES_SECRET_KEY_BYTES,"AES"));
byte[] result = cipher.doFinal(dataBytes);
deToString(result);
}catch(Exception e){
<("执⾏CodecUtil.aesEncrypt失败:data={},异常:{}", data, e);
}
return null;
}
public static String aesDecrypt(String encryptedDataBase64){
try{
Cipher cipher = Instance("AES/ECB/PKCS5Padding");// 加密算法/⼯作模式/填充⽅式
byte[] dataBytes = Base64Utils.decodeFromString(encryptedDataBase64);
cipher.init(Cipher.DECRYPT_MODE,new SecretKeySpec(AES_SECRET_KEY_BYTES,"AES"));
byte[] result = cipher.doFinal(dataBytes);
return new String(result);
}catch(Exception e){
<("执⾏CodecUtil.aesDecrypt失败:data={},异常:{}", encryptedDataBase64, e);
}
return null;
}
使⽤SHA1加密
public static String sha1Encrypt(String data){
return DigestUtils.sha1Hex(data + SHA1_SECRET_KEY);
}
前端加密⽰例,这⾥是⼀个登陆的请求例⼦,先对数据进⾏加密,再⽤加密数据同时间戳和提⾼复杂度的AES密钥使⽤SHA1加密⽣成签名,最终将数据组装发送到后台。
注意这⾥有两个AES密钥,需要和后台对应。
$(function(){
$("#login_submit").click(function(){
var username =$("#username").val();
var password =$("#password").val();
if(username != undefined && username !=null&& username !=""
&& password != undefined && password !=null&& password !=""){
var loginJSON =JSON.stringify({"username": username,"password": password});
var encryptedData = pt(loginJSON, Base64.parse('XjjkaLnlzAFbR399IP4kdQ=='),{
mode: de.ECB,
padding: CryptoJS.pad.Pkcs7,
length:128
}).toString();
var timestamp =new Date().getTime();
var sign = CryptoJS.SHA1(encryptedData + timestamp +"QGZUanpSaSy9DEPQFVULJQ==").toString();
$.ajax({
url:"/user/login",
contentType:"application/json",
type:"post",
data:JSON.stringify({"sign": sign,"encryptedData": encryptedData,"timestamp": timestamp}),
dataType:"json",
success:function(data){
}
});
});
2)解密实现
⾸先创建⼀个类⽤于接收前端传过来的加密请求。
@Data
public class EncryptedReq<T>{
/** 签名 */
@NotBlank(message ="⽤户签名不能为空")
private String sign;
/** 加密请求数据 */
@NotBlank(message ="加密请求不能为空")
private String encryptedData;
/** 原始请求数据(解密后回填到对象) */
private T data;
/** 请求的时间戳 */
@NotNull(message ="时间戳不能为空")
private Long timestamp;
}
这⾥将使⽤AOP在切⾯中进⾏解密的操作,⾸先创建注解,在接收加密请求的接⼝⽅法上添加该注解,然后对该⽅法的EncryptedReq参数进⾏验签及解密。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptAndVerify {
/** 解密后的参数类型 */
Class<?>decryptedClass();
}
AOP代码如下,具体步骤就是参数校验,验证及解密,数据回填。
@Slf4j
@Aspect
@Component
public class DecryptAndVerifyAspect {
@Pointcut("@annotation(com.dfy.auth.annotation.DecryptAndVerify)")
public void pointCut(){}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint)throws Throwable {
Object[] args = Args();
if(args == null || args.length ==0){
throw new Signature().getName()+",参数为空");
}
EncryptedReq encryptedReq = null;
for(Object obj : args){
if(obj instanceof EncryptedReq){
encryptedReq =(EncryptedReq) obj;
break;
}
}
if(encryptedReq == null){
throw new Signature().getName()+",参数中⽆待解密类");
}
String decryptedData =decryptAndVerify(encryptedReq);
MethodSignature methodSignature =(MethodSignature) Signature();
DecryptAndVerify annotation = Method().getAnnotation(DecryptAndVerify.class);
if(annotation == null || annotation.decryptedClass()== null){
throw new Signature().getName()+",未指定解密类型");
}
encryptedReq.setData(JSON.parseObject(decryptedData, annotation.decryptedClass()));
return joinPoint.proceed();
}
private String decryptAndVerify(EncryptedReq encryptedReq){
String sign = CodecUtil.EncryptedData()+ Timestamp());
if(sign.Sign())){
return CodecUtil.EncryptedData());
}else{
throw new DecryptAndVerifyException("验签失败:"+ JSONString(encryptedReq));
}
}
}
最后写⼀个接⼝进⾏测试
@PostMapping("/login")
@ResponseBody
@DecryptAndVerify(decryptedClass = UserLoginReq.class)
public ResponseVo login(@RequestBody@Validated EncryptedReq<UserLoginReq> encryptedReq, HttpServletRequest request){
UserLoginReq userLoginReq = Data();
// TODO 从数据库核实⽤户登录信息,这⾥懒得查数据库了
Username().equals("admin")&& Password().equals("admin")){
Success();
}else{
Failure(ResponseStatusEnum.USER_AUTH_FAILURE);
}
}
访问localhost:8080/user/login,传⼊加密数据,即可获得正确的响应(加密过程⼯具类有,这⾥不展⽰了)
{"encryptedData":"AN8LpQrOTFEFi8l4MQYyYriUDsKTwLhWtkaI9q6Ck/zjlm1PY/5rQObOeOAFBipY","sign":"ba8dac258b7802b9a407911524ba6f8448e8e a25","timestamp":1585702530560}
3)请求拦截
这⾥从上往下开始介绍,先介绍配置类。
这⾥配置了拦截所有路径,然后添加了三个启动参数,⽤于在中获取并分别进⾏处理。
@Configuration
public class MainConfig {
/** 不作拦截的URL路径 */
private String excludedURLPaths ="/index,/user/login,/user/register,/**/*.jpg,/**/*.css,/test/**";
private String logoutURL ="/user/logout";
private String loginURI ="/user/login";
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean filterRegistrationBean =new FilterRegistrationBean();
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setFilter(new AuthFilter());
filterRegistrationBean.addInitParameter(AuthConstants.EXCLUDED_URI_PATHS, excludedURLPaths);
filterRegistrationBean.addInitParameter(AuthConstants.LOGOUT_URI, logoutURL);
filterRegistrationBean.addInitParameter(AuthConstants.LOGIN_URI, loginURI);
return filterRegistrationBean;
}
}
接下来介绍,具体逻辑就是判断请求是否需要拦截,如果需要判断⽤户是否登录,如果已登录判断是否为登出
public class AuthFilter implements Filter {
private static String loginURI;
springboot aop
private static String logoutURI;
/** ⽤于识别出不需要拦截的URI */
private static ExcludedURIUtil excludedURIUtil;
@Override
public void init(FilterConfig filterConfig)throws ServletException {
String[] excludedURLPaths = InitParameter(AuthConstants.EXCLUDED_URI_PATHS).split(",");
excludedURIUtil = Instance(excludedURLPaths);
logoutURI = InitParameter(AuthConstants.LOGOUT_URI);
loginURI = InitParameter(AuthConstants.LOGIN_URI);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {        HttpServletRequest request =(HttpServletRequest) servletRequest;
HttpServletResponse response =(HttpServletResponse) servletResponse;
String requestURI = RequestURI();
if(excludedURIUtil.match(requestURI)){// 如果请求URI不需要进⾏拦截
filterChain.doFilter(request, response);
return;
}
if(!UserLoginUtil.verify(request)){// 如果⽤户未登录
response.sendRedirect(loginURI);
}else{
if(requestURI.equals(logoutURI)){// ⽤户登出时删除相关数据
UserLoginUtil.logout(request);
}
filterChain.doFilter(request, response);
}
}
}
判断URI是否需要拦截这⾥使⽤的是正则表达式,将不需要拦截的URI转换为正则存起来,之后直接⽤请求的URI来匹配 (这⾥的正则也是现学现⽤,百度了半天写出来的,如果有更好的,可替代)
public class ExcludedURIUtil {
/** 单例 */
private static ExcludedURIUtil excludedUriUtil;
/** uri、正则表达式映射表 */
private static Map<String, String> uriRegexMap =new HashMap<String, String>(); private ExcludedURIUtil(){}
public static ExcludedURIUtil getInstance(String[] uris){
if(excludedUriUtil == null){
synchronized(ExcludedURIUtil.class){
if(excludedUriUtil == null){
excludedUriUtil =new ExcludedURIUtil();
if(uris != null && uris.length >0){
for(String uri : uris){
String regex =uri2Regex(uri);
uriRegexMap.put(uri, regex);
}
}
}
}
}
return excludedUriUtil;
}
/**
* 判断给定uri是否匹配映射表中的正则表达式
*/
public boolean match(String uri){
for(String regex : uriRegexMap.values()){
if(uri.matches(regex)){
return true;
}
}
return false;
}
/**
* 将URI转换为正则表达式
*/
public static String uri2Regex(String uri){
int lastPointIndex = uri.lastIndexOf('.');
char[] uriChars = CharArray();
StringBuilder regexBuilder =new StringBuilder();
for(int i =0, length = uriChars.length; i < length; i++){
if(uriChars[i]=='*'&& uriChars[i +1]=='*'){
regexBuilder.append("(/[^/]*)*");
i++;
}else if(uriChars[i]=='*'){
regexBuilder.append("/[^/]*");
}else if(uriChars[i]=='.'&& i == lastPointIndex){
regexBuilder.append("\\.");
regexBuilder.append(uri.substring(i +1));
break;
}else if(uriChars[i]=='/'){
if(!uri.substring(i +1, i +2).equals("*")){
regexBuilder.append("/");
}
}else{
regexBuilder.append(uriChars[i]);
}
}
String();
}
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。