SpringBoot using custom encryption and decryption parameters simple annotations (notes + HandlerMethodArgumentResolver)

Foreword

Huang Han-three and I came back, six months earlier did not update the blog, the experience of the past six months it is not easy,
epidemic head, I did not practice the company through thick and thin with the staff, a lot of people quit directly.
As an intern, I have been ruthlessly expelled. So you also have to re-prepare to find a job.
Forget it, feeling it, had wanted issued yesterday, but yesterday was clear, mourning period, it remains to this day hair.

Man of few words said, directly get to the bar. During this time I was writing complete set up, and the school has yet to school news, true hard top.
Originally I just wanted to be opened back to school safely "old age" Well, after all, can find work, but friends and family after graduation rarely met.
So parents who must cherish the people around Oh.

Because this blog is now written local typora above and then let the blog Park, unified format a little
blog garden markdown editor is not very good, this is a bit headache
there is one point code format issue, and copied to the markdown confounded
I cried, already in turmoil, coupled with the blog space issue a squeeze, Bowen chaos is over
after more markdown files are used, so the question of what the layout will be more landscaping

In this article the reader will be able to learn the following

  • Simple to use and analytical annotations
  • HandlerMethodArgumentResolver the relevant part of the knowledge

Due

Write complete set, this week only then setting up the background, there is a small program end has not yet begun. As the title says, the SpringBoot do with the back-end build.
Then of course it applies for RESTful, when I have a url is / initJson / {id}, when he passed directly over the user ID.
I wanted to be able to simply look at the front-end encrypted ID, at least not watch ID directly spread to the back-end. Although only a complete set,
but still a little something about it, if I choose to deal with Base64 better.
Now I would like some simple parameter passed to the front end, and then transmitted to the rear end with the ciphertext decryption, to avoid transmitting the plaintext.
Of course, in a real environment, certainly use a better program. Here we are just saying there are so kind of thinking or that scenario.
You can initiate after give you an example.

process

1. The front end

When the front end parameter passing, encryption

 // encode是Base64加密的方法,可以自己随便整一个
 data.password = encode(pwd);
 data.username= encode(username);

This way the front end of the ciphertext is passed in the past.

2. backend

When the parameters passed to the back-end, you want to resolve the ciphertext back to plaintext, then the next is the subject of this paper lies.
When decrypted, it began as an interface inside decryption.

/**
  *  此时参数接受到的内容是密文
  */
String login(String username, String password) {
       username =  Base64Util.decode(username);
       password=  Base64Util.decode(password);
}

Nothing looks right, but in case of many parameters, interfaces or more, do you want to write code each interface so a bunch of decrypting it.
It will also be the transformation, how do? I think the comments, or would like to try to use notes, so I could deepen the study notes.

2.1 Notes

I note this thing, when I thought it was time to learn how to function, the original is that you can customize (laughing cry).
In this paper, we annotate it simple to understand, if necessary, I can update the blog behind an article about annotations.
Or readers can learn on their own to find out, at this point, the reason I write the blog is not online, or something found on the Internet now will write a blog not the same as when I want.
Do not write anything, so all the same thing, so I updated the blog is not much, basically a long time.
But it seems to think is not right, no matter what is written blog content, not only to facilitate their learning can be convenient for others,
so that they should update frequency hope it will be better.

Back to the topic, notes there are three main things

  • Annotation defines (Annotation)
  • Type of annotation (ElementType)
  • Notes strategy (RetentionPolicy)

Let's look at the definition of notes, very simple

// 主要的就是 @interface  使用它定义的类型就是注解了,就跟class定义的类型是类一样。
public @interface Base64DecodeStr {
    /**
     * 这里可以放一些注解需要的东西
     * 像下面这个count()的含义是解密的次数,默认为1次
     */
    int count() default 1;
}

Then look at the types of annotations

// 注解类型其实就是注解声明在什么地方
public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */
    FIELD,              /* 字段声明(包括枚举常量)  */
    METHOD,             /* 方法声明  */
    PARAMETER,          /* 参数声明  */
    CONSTRUCTOR,        /* 构造方法声明  */
    LOCAL_VARIABLE,     /* 局部变量声明  */
    ANNOTATION_TYPE,    /* 注释类型声明  */
    PACKAGE             /* 包声明  */
}

// 这个Target就是这么使用的
// 现在这个注解,本人希望它只能声明在方法上还有参数上,别的地方声明就会报错
@Target({ElementType.METHOD, ElementType.PARAMETER})
public @interface Base64DecodeStr {
    int count() default 1;
}

Finally, look at the annotations strategy

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;
}

So far, a comment on the definition of good. But at what point do the work, then we need to write a parse this comment.
Then think about it, the definition of the purpose of this comment, the parameter directly in the interface is plain text, so it should be put back to plaintext and ciphertext decryption parameters back inside before entering the interface.
This step is there any good way to do that this time the turn of the next stage of the protagonist, it is HandlerMethodArgumentResolver.

2.2 HandlerMethodArgumentResolver

On the role of HandlerMethodArgumentResolver and resolution, is so written official

/**
 * 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;
}

So this interface, it is important to think about why SpringMVC to write a few notes in the controller, can receive the parameters, this interface is withheld.
Like common @PathVariable it is to use this interface implementation.

I understand that implement this interface will be able to approach the front end and the rear end of the interface between the parameters, it is just to meet the above requirements.
In fact, this interface is also part of a common SpringMVC source inside, readers still can inform themselves about, the
current Spring I am not ready to write the source code to read the article, because I also have not seen the system go, perhaps later I read on will be updated about the blog.

Continue, with such an interface can be used to write a custom annotation resolve, attentive students can be found here writing analytical notes,
then the notes can only play a role in the control layer, DAO layer even in the service layer are impossible, so if you want to use globally, then
I think that you can cut it with AOP, where the need to use all cut up on it.

HandlerMethodArgumentResolver implement interfaces to write resolution.

public class Base64DecodeStrResolver implements HandlerMethodArgumentResolver {

    private static final transient Logger log = LogUtils.getExceptionLogger();

    /**
     * 如果参数上有自定义注解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)
                ? parameter.getMethodAnnotation(Base64DecodeStr.class).count()
                : parameter.getParameterAnnotation(Base64DecodeStr.class).count();
        /**
         * 如果是实体类参数,就把前端传过来的参数构造成一个实体类
         * 在系统中本人把所有实体类都继承了BaseEntity
         */
            if (BaseEntity.class.isAssignableFrom(parameter.getParameterType())) {
                Object obj = parameter.getParameterType().newInstance();
                webRequest.getParameterMap().forEach((k, v) -> {
                    try {
                        BeanUtils.setProperty(obj, k, decodeStr(v[0], count));
                    } catch (Exception e) {
                        log.error("参数解码有误", e);
                    }
                });
                // 这里的return就会把转化过的参数赋给控制器的方法参数
                return obj;
                // 如果是非集合类,就直接解码返回
            } else if (!Iterable.class.isAssignableFrom(parameter.getParameterType())) {
                return decodeStr(webRequest.getParameter(parameter.getParameterName()), 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 then registered what this custom.
Here it is not a registered profile

@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
}

Use annotations on the controller layer.

/**
 * 先试试给方法加注解
 */
@Base64DecodeStr 
public void login(@NotBlank(message = "用户名不能为空")  String username,
                   @NotBlank(message = "密码不能为空") String password) {
            System.out.println(username);
            System.out.println(password);
    }

See results

  • Distal traditional values
  • Back-end reception

At this point the whole function has been achieved, we look at the key api

// 这个就是一个参数,控制层的方法参数
MethodParameter parameter
    // 常用方法
    hasMethodAnnotation()  是否有方法注解
    hasParameterAnnotation()  是否有参数注解
    getMethodAnnotation()  获取方法注解(传入Class可以指定)
    getParameterAnnotation() 获取参数注解(传入Class可以指定)
    getParameterType()  获取参数类型


// 这个可以理解为是前端传过来的东西,里面可以拿到前端传过来的密文,也就是初始值,没有被处理过的
NativeWebRequest webRequest
    // 常用方法 其实这几个都是同一个 基于map的操作
    getParameter()  
    getParameterMap()
    getParameterNames()
    getParameterValues()

2.3 depth study

The above example is in the annotation process, try next annotation on parameters.

/**
 * 注解一个参数
 */
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

Notes can also be seen on the parameter, then look again at the same time annotation on the method and parameters, think about it.
Notes on the assumption that the preferred method, followed by notes on the parameters, will be parsed twice,
that is, the first cipher text into plain text is annotated method resolved, then parsed again after being parameter annotation into something else.

/**
 * 注解方法 注解参数
 */
@Base64DecodeStr
public void login(@NotBlank(message = "用户名不能为空") @Base64DecodeStr  String username,
                   @NotBlank(message = "密码不能为空") @Base64DecodeStr String password) {
            System.out.println(username);
            System.out.println(password);
}
/*****************输出******************************/
username
password

Plain text output is correct, that the above assumption does not hold, let us Kankan where the problem is.

Recall that at the time of the analysis, we are using webRequestthe getParameter, and webRequestwhich values are to take over from the front,
so decodeStr decryption is decrypted on the front of the value, of course, will return the correct content (plain text), so even Notes is a method to decrypt, decrypt it is the value of the front end,
and then to attribute notes, the value of its front end is decrypted, the decrypted content attribute annotation does not appear that the annotated method to decrypt the contents out.
From this point of view, it is indeed such a thing, even if the method parameter annotations and notes, together with the effect of repeated decryption does not appear.

However, this is only one reason, at the beginning I did not think of this, then hit a breakpoint curious to track down the source.

@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 [" +
		  parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
		}
		  return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
                // argumentResolverCache是一个缓存,map,
                // 从这里可以看出,每个控制器方法的参数都会被缓存起来,
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(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;          
}


So the question then refine it, when we also annotate methods and parameters, calls several times getArgumentResolver () it,
for ease of viewing, annotation I will pass different parameters.
Before that, first put a small episode, is found when debugging problems

/**
 * 注解方法
 */
@Base64DecodeStr( count = 10)
public void login(@NotBlank(message = "用户名不能为空") String username,
                   @NotBlank(message = "密码不能为空")  String password) {
            System.out.println(username);
            System.out.println(password);
}

Go before the

parameter is less than the method of obtaining this custom annotations.
When the code to go down, go supportsParameter when

this time there, and silent.
What is the reason I did not find temporary.

Closer to home, we continue to debug

/**
 * 注解方法 注解全部参数
 */
@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);
}

Or go to see the annotated method parameter annotation.

  • First come in

    can see the first parameter username
  • The second coming

    is still the first parameter username
  • The third came in

    to see the second parameter password
  • Fourth came

    also the second parameter password

So you can see, there is no method to go annotations or annotated method will go twice, once a parameter annotation, so a total of four times, which is no problem.
This is how it happened. If it does not go annotated method, a method that notes how it takes effect, I found the reason behind

  /**
   * 原来是因为这里,虽然不是因为方法注解进来的,但是这里优先取的是方法注解的值,
   * 所以如果想让属性注解优先的话这里改一下就行
   */
  int count = parameter.hasMethodAnnotation(Base64DecodeStr.class)
                ? parameter.getMethodAnnotation(Base64DecodeStr.class).count()
                : parameter.getParameterAnnotation(Base64DecodeStr.class).count();

So the truth is out, if the method annotations and notes at the same time add attributes, then, will be performed four times getArgumentResolver (),
which only called twice supportsParameter (), because the second time to take the value of each parameter directly from the map on the no longer go supportsParameter () a.

End

So far we have completed this journey, front to back.
Briefly summarize.

  • annotation
    • Definitions: @interface
    • 类型:TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE
    • Strategy: SOURCE, CLASS, RUNTIME
  • HandlerMethodArgumentResolver
    • Action: as interceptors, the rear end of the front end to the middle level
    • Two methods
      • supportsParameter: whether to support the use of the Resolver
      • resolveArgument: Resolver want to do something

Then comment on the Resolution section also is not perfect, for example, if the parameter type is a collection of words should be how to deal with, this is a follow-up.

In this part are the real problems I encountered and recorded from the beginning want to encrypt the encryption parameters to find ways to achieve this function,
such an idea, I hope to give new people a little inspiration, of course, we also need to keep learning itself , or can not find work, I can only complete set side edges busy to spend time reviewed.
A melancholy man, then the more, hey, not long-winded, and now is the night two points, ready to sleep.

Guess you like

Origin www.cnblogs.com/yellowgg/p/12635553.html