SpringBoot使⽤⾃定义注解实现简单参数加密解密(注解+HandlerMethodA。。
。
前⾔
我黄汉三⼜回来了,快半年没更新博客了,这半年来的经历实属不易,
疫情当头,本⼈实习的公司没有跟员⼯共患难,直接辞掉了很多⼈。
作为⼀个实习⽣,本⼈也被⽆情开除了。所以本⼈⼜得重新准备⼯作了。
算了,感慨⼀下,本来想昨天发的,但昨天是清明,哀悼时期,就留到了今天发。
话不多说,直接进⼊正题吧。这段时间本⼈在写毕设,学校也迟迟没有开学的消息,属实难顶。
本来被开了本⼈只想回学校安度"晚年"算了,毕竟⼯作可以再,但亲朋好友以后毕业了就很少见了。
所以亲们,⼀定要珍惜⾝边的⼈噢。
因为这篇博⽂是现在本地typora上⾯写好再放过博客园的,格式有点不统⼀
博客园的markdown编辑器还不是很好⽤,这点有点头疼
还有⼀点是代码格式问题,复制到markdown⼜变乱了
我哭了,本来就乱了,再加上博客篇幅的问题⼀挤压,博⽂就乱完了
以后更⽂都⽤markdown了,所以关于排版的问题会越来越美化⼀下
通过本⽂读者将可以学习到以下内容
注解的简单使⽤和解析
HandlerMethodArgumentResolver相关部分知识
起因
写毕设,这周才把后台搭好,还有⼩程序端还没开始。如题⽬所说,⽤了SpringBoot做后端搭建。
然后也当然应⽤了RESTful风格,当本⼈有⼀个url是/initJson/{id}的时候,直接就把⽤户ID传过来了。
本⼈就想能不能在前端简单把ID加密⼀下,起码不能眼睁睁看着ID直接就传到后端。虽然只是⼀个毕设,
但还是稍微处理⼀下吧,处理的话我选择⽤Base64好了。
本⼈现在是想把前端传的⼀些简单参数,⽤密⽂传到后端再解密使⽤,避免明⽂传输。
当然在真正的环境中,肯定是使⽤更好的⽅案的。这⾥只是说有那么⼀种思路或者说那么⼀种场景。
给⼤家举个例⼦之后可以抛砖引⽟。
过程
1.前端
前端传参的时候,加密
// encode是Base64加密的⽅法,可以⾃⼰随便整⼀个
data.password = encode(pwd);
data.username= encode(username);
这样⼦前端传过去就是密⽂了。
2.后端
当参数传到后端之后,想要把密⽂解析回明⽂,然后接下来就是本⽂的主旨所在了。
解密的时候,本⼈⼀开始是在接⼝⾥⾯解密的。
/**
* 此时参数接受到的内容是密⽂
*/
String login(String username, String password) {
username = Base64Util.decode(username);
password= Base64Util.decode(password);
}
看起来也没啥是吧,但是万⼀参数很多,或者说接⼝多,难道要每个接⼝都这么写⼀堆解密的代码吗。
显然还可以改造,怎么做?本⼈想到了注解,或者说想⽤注解试试,这样⾃⼰也能加深对注解的学习。
2.1 注解
注解这个东西,本⼈当时学习的时候还以为是怎么起作⽤的,原来是可以⾃定义的(笑哭)。
我们在本⽂简单了解下注解吧,如果有需要,后⾯本⼈可以更新⼀篇关于注解的博⽂。
或者读者可以⾃⾏学习了解⼀下,说到这⾥,本⼈写博客的理由是,⽹上没有,或者⽹上到的东西跟本⼈需要的不⼀样时才会写博客。
有的话就不写了,以免都是同样的东西,所以本⼈更新的博客并不算多,基本很久才⼀篇。
但好像这样想并不对,写博客⽆论是什么内容,不仅⽅便⾃⼰学习也可以⽅便他⼈,
所以以后应该更新频率会好点吧希望。
回到正题,注解有三个主要的东西
注解定义(Annotation)
注解类型(ElementType)
注解策略(RetentionPolicy)
先来看看注解定义,很简单
// 主要的就是 @interface 使⽤它定义的类型就是注解了,就跟class定义的类型是类⼀样。
public @interface Base64DecodeStr {
/**
* 这⾥可以放⼀些注解需要的东西
* 像下⾯这个count()的含义是解密的次数,默认为1次
*/
int count() default 1;
}
然后再来看看注解类型
// 注解类型其实就是注解声明在什么地⽅
public enum ElementType {
TYPE, /* 类、接⼝(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* ⽅法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造⽅法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
springboot aop// 这个Target就是这么使⽤的
// 现在这个注解,本⼈希望它只能声明在⽅法上还有参数上,别的地⽅声明就会报错
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Base64DecodeStr {
int count() default 1;
}
最后再来看看注解策略
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了*/
CLASS, /* 编译器将Annotation存储于类对应的.class⽂件中。默认⾏为 */
RUNTIME /* 编译器将Annotation存储于class⽂件中,并且可由JVM读⼊ */
}
// ⼀般⽤第三个,RUNTIME,这样的话程序运⾏中也可以使⽤
@Target({ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Base64DecodeStr {
int count() default 1;
}
到此为⽌,⼀个注解就定义好了。但是在什么时候⼯作呢,这时我们就需要写这个注解的解析了。
然后想想,定义这个注解的⽬的是,想直接在接⼝使⽤参数就是明⽂,所以应该在进⼊接⼝之前就把密⽂解密回明⽂并放回参数⾥。
这⼀步有什么好办法呢,这时候就轮到下⼀个主⾓登场了,它就是HandlerMethodArgumentResolver。
2.2 HandlerMethodArgumentResolver
关于HandlerMethodArgumentResolver的作⽤和解析,官⽅是这么写的
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
* 翻译了⼀下
* 策略接⼝,⽤于在给定请求的上下⽂中将⽅法参数解析为参数值
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {
/**
* MethodParameter指的是控制器层⽅法的参数
* 是否⽀持此接⼝
* ture就会执⾏下⾯的⽅法去解析
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 常见的写法就是把前端的参数经过处理再复制给控制器⽅法的参数
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
所以这个接⼝,是很重要的,想想SpringMVC为何在控制器写⼏个注解,就能接收到参数,这个接⼝就是功不可没的。
像常见的@PathVariable 就是⽤这个接⼝实现的。
本⼈的理解是,实现这个接⼝,就能在前端到后端接⼝之间处理⽅法和参数,所以刚好满⾜上⾯的需求。
其实这个接⼝也是属于SpringMVC源码⾥⾯常见的⼀个,读者依然也可⾃⾏了解下,
⽬前本⼈还没有准备要写Spring读源码的⽂章,因为本⼈也还没系统的去看过,或许以后本⼈看了就会更新有关博客。
继续,有了这样的接⼝就可以⽤来写解析⾃定义注解了,细⼼的同学可以发现,在这⾥写注解解析,
那么这个注解就只能是在控制层起作⽤了,在服务层甚⾄DAO层都⽤不了,所以如果想全局⽤的话,
本⼈想到的是可以⽤AOP切⼀下,把需要⽤到的地⽅都切起来就可以了。
实现HandlerMethodArgumentResolver接⼝来写解析。
public class Base64DecodeStrResolver implements HandlerMethodArgumentResolver {
private static final transient Logger log = ExceptionLogger();
/**
* 如果参数上有⾃定义注解Base64DecodeStr的话就⽀持解析
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(Base64DecodeStr.class)
|| parameter.hasMethodAnnotation(Base64DecodeStr.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
/**
* 因为这个注解是作⽤在⽅法和参数上的,所以要分情况
*/
int count = parameter.hasMethodAnnotation(Base64DecodeStr.class)
: ParameterAnnotation(Base64DecodeStr.class).count();
/**
* 如果是实体类参数,就把前端传过来的参数构造成⼀个实体类
* 在系统中本⼈把所有实体类都继承了BaseEntity
*/
if (BaseEntity.class.ParameterType())) {
Object obj = ParameterType().newInstance();
try {
BeanUtils.setProperty(obj, k, decodeStr(v[0], count));
} catch (Exception e) {
<("参数解码有误", e);
}
});
// 这⾥的return就会把转化过的参数赋给控制器的⽅法参数
return obj;
// 如果是⾮集合类,就直接解码返回
} else if (!Iterable.class.ParameterType())) {
return ParameterName()), count);
}
return null;
}
/**
* Base64根据次数恢复明⽂
*
* @param str Base64加密*次之后的密⽂
* @param count *次
* @return 明⽂
*/
public static String decodeStr(String str, int count) {
for (int i = 0; i < count; i++) {
str = Base64.decodeStr(str);
}
return str;
}
}
然后注册⼀下这个⾃定义的Resolver。
这⾥就不⽤配置⽂件注册了
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
//region 注册⾃定义HandlerMethodArgumentResolver
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(base64DecodeStrResolver());
}
@Bean
public Base64DecodeStrResolver base64DecodeStrResolver() {
return new Base64DecodeStrResolver();
}
//endregion
}
在控制器层使⽤注解。
/**
* 先试试给⽅法加注解
*/
@Base64DecodeStr
public void login(@NotBlank(message = "⽤户名不能为空") String username,
@NotBlank(message = "密码不能为空") String password) {
System.out.println(username);
System.out.println(password);
}
看看效果
前端传值
后端接收
⾄此整个功能上已经实现了,我们来看下关键api
// 这个就是⼀个参数,控制层的⽅法参数
MethodParameter parameter
/
/ 常⽤⽅法
hasMethodAnnotation() 是否有⽅法注解
hasParameterAnnotation() 是否有参数注解
getMethodAnnotation() 获取⽅法注解(传⼊Class可以指定)
getParameterAnnotation() 获取参数注解(传⼊Class可以指定)
getParameterType() 获取参数类型
// 这个可以理解为是前端传过来的东西,⾥⾯可以拿到前端传过来的密⽂,也就是初始值,没有被处理过的NativeWebRequest webRequest
// 常⽤⽅法其实这⼏个都是同⼀个基于map的操作
getParameter()
getParameterMap()
getParameterNames()
getParameterValues()
2.3 深⼊探讨
上⾯的例⼦是注解在⽅法上的,接下来试试注解在参数上。
/**
* 注解⼀个参数
*/
public void login(@NotBlank(message = "⽤户名不能为空") @Base64DecodeStr String username,
@NotBlank(message = "密码不能为空") String password) {
System.out.println(username);
System.out.println(password);
}
/
*****************输出******************************/
username
WTBkR2VtTXpaSFpqYlZFOQ==
/**
* 注解两个参数
*/
public void login(@NotBlank(message = "⽤户名不能为空") @Base64DecodeStr String username,
@NotBlank(message = "密码不能为空") @Base64DecodeStr String password) {
System.out.println(username);
System.out.println(password);
}
/
*****************输出******************************/
username
password
可见注解在参数上也能⽤,接下来再来看看,同时注解在⽅法上和参数上,想⼀下。
假设⽅法上的注解优先,参数上的注解其次,会不会被解析两次,
也就是说,密⽂先被⽅法注解解析成明⽂,然后之后被参数注解再次解析成别的东西。
/**
* 注解⽅法注解参数
*/
@Base64DecodeStr
public void login(@NotBlank(message = "⽤户名不能为空") @Base64DecodeStr String username,
@NotBlank(message = "密码不能为空") @Base64DecodeStr String password) {
System.out.println(username);
System.out.println(password);
}
/*****************输出******************************/
username
password
输出的是正确的明⽂,也就是说上⾯的假设不成⽴,让我们康康是哪⾥的问题。
回想⼀下,在解析的时候,我们都是⽤的webRequest的getParameter,⽽webRequest⾥⾯的值是从前端拿过来的,
所以decodeStr解密都是对前端的值解密,当然会返回正确的内容(明⽂),所以即使是⽅法注解先解密了,它解密的是前端的值,
然后再到属性注解,它解密的也是前端的值,不会出现属性注解解密的内容是⽅法注解解密出来的内容。
从这点来看,确实是这么⼀回事,所以即使⽅法注解和参数注解⼀起⽤也不会出现重复解密的效果。
但是,这只是⼀个原因,⼀开始本⼈还没想到这个,然后就好奇打了断点追踪下源码。
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // 获取参数的resolver,参数的定位是控制器.⽅法.参数位置,所以每个parameter都是唯⼀的
// ⾄于重载的啊,不知道没试过,你们可以试下,XD
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
}
solveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
// argumentResolverCache是⼀个缓存,map,
// 从这⾥可以看出,每个控制器⽅法的参数都会被缓存起来,
HandlerMethodArgumentResolver result = (parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
// 调⽤supportsParameter看看是否⽀持
if (resolver.supportsParameter(parameter)) {
result = resolver;
// ⼀个参数可以有多个resolver
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
所以问题再细化⼀点,当我们同时注解⽅法和参数的时候,会调⽤⼏次getArgumentResolver()呢,
为了便于观察,本⼈将注解传不同的参数。
在那之前,先放点⼩插曲,就是在调试的时候发现的问题
/**
* 注解⽅法
*/
@Base64DecodeStr( count = 10)
public void login(@NotBlank(message = "⽤户名不能为空") String username,
@NotBlank(message = "密码不能为空") String password) {
System.out.println(username);
System.out.println(password);
}
进去前
parameter是获取不到⽅法上这个⾃定义注解的。
当代码往下⾛,⾛到supportsParameter的时候
此时⼜有了,⽆语。
什么原因本⼈暂时没到。
⾔归正传,我们继续调试
/**
* 注解⽅法注解全部参数
*/
@Base64DecodeStr( count = 30)
public void login(@NotBlank(message = "⽤户名不能为空") @Base64DecodeStr(count = 10) String username,
@NotBlank(message = "密码不能为空") @Base64DecodeStr(count =20) String password) {
System.out.println(username);
System.out.println(password);
}
看看是先⾛⽅法注解还是参数注解。
第⼀次进来
可以看到是第⼀个参数username
第⼆次进来
依然是第⼀个参数username
第三次进来
看到是第⼆个参数password
第四次进来
也是第⼆个参数password
所以可以看到,根本就没有⾛⽅法注解,或者说⽅法注解会⾛两次,参数注解⼀个⼀次,所以总共四次,这也没问题。
这是怎么回事呢。要是不⾛⽅法注解,那⽅法注解怎么会⽣效呢,后⾯我到了原因
/**
* 原来是因为这⾥,虽然不是因为⽅法注解进来的,但是这⾥优先取的是⽅法注解的值,
* 所以如果想让属性注解优先的话这⾥改⼀下就⾏
*/
int count = parameter.hasMethodAnnotation(Base64DecodeStr.class)
: ParameterAnnotation(Base64DecodeStr.class).count();
所以真相⼤⽩了,如果⽅法注解和属性注解同时加上的话,会执⾏四次getArgumentResolver(),
其中只会调⽤两次supportsParameter(),因为每个参数第⼆次都直接从map取到值了就不再⾛supportsParameter()了。
结束
⾄此我们完成了本次从前端到后端的旅途。
简单总结⼀下。
注解
定义:@interface
类型:TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE 策略:SOURCE,CLASS,RUNTIME
HandlerMethodArgumentResolver
作⽤:像⼀样,在前端到后端中间的关卡
两个⽅法
supportsParameter:是否⽀持使⽤该Resolver
resolveArgument:Resolver想要做的事
然后关于注解解析部分也不够完善,⽐如如果参数是集合类型的话应该怎么处理,这都是后续了。
本篇内容都是本⼈真实遇到的问题并记录下来,从开始想要加密加密参数到想办法去实现这个功能,
这么⼀种思路,希望能给新⼈⼀点启⽰,当然本⼈本⾝也还需要不断学习,不然都不到⼯作了,我只能边忙毕设边挤时间复习了。
⼈⼀惆怅话就多了,嘿嘿,不啰嗦了,现在是夜⾥两点,准备睡了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论