springboot的资源请求验证

springboot的资源请求验证

基于SpringBoot的资源请求验证(Aspectj和Interceptor两方式实现)附JWT验证token
前言
​ 在项目中,我们需要对前端请求的资源进行验证,判断是否具有相应的权限。比如某写资源只有在登录之后才有请求权限。本章以请求之前是否登录为权限。

​ 解决方法就是在请求到达controller之前进行拦截,判断该用户是否登录,如果未登录则直接返回,如果已登录则“放行”,去执行该请求本来要请求的controller

示例图:

QQ20200420-111124@2x

aspectj和interceptor知识准备

aspectj

aspectj是spring AOP的一种静态代理,静态代理唯一的缺点就是我们需要对每一个方法编写我们的代理逻辑,造成了工作的繁琐和复杂。AspectJ就是为了解决这个问题,在编译成class字节码的时候在方法周围加上业务逻辑。复杂的工作由特定的编译器帮我们做。(这里不是很了解java的代理的是同学可以去百度一下)
aspectj提供前置通知,后置通知和环绕通知等。

interceptor

SpringMVC提供的一种拦截器
​ 1.定义一个类实现HandleInterceptor重写里面的方法,该接口对里面的方法有默认实现。

​ 2.定义一个类实现WebMvcConfigurer,实现addInterceptors()方法注册interceptor,并添加拦截路径(后面还会提到使用注解的方式标注controller进行了请求拦截)。

使用aspectj实现

引入aspectj的依赖

 	<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

自定义注解

//需要登录
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedLogin {
}

//不需要登录
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassLogin {
}

切面定义

**
 * 创建aspect,不需要继承/实现任何类和接口。
 * 可以拦截请求,并通过springframeword的RequestContextHolder
 *
 * 使用aspect对请求的拦截和处理
 */
@Aspect
@Component
public class ForVerifyAsp {

    @Pointcut("@annotation(com.wdg.my_annotation.NeedLogin)")
    public void pointCut(){}

    @Around("@annotation(com.wdg.my_annotation.PassLogin)")
    public Object verify(ProceedingJoinPoint point){
        Object[] args = point.getArgs();//获得参数

        /**
         * 获得请求的方法和类一便通过后执行请求的方法,(asp的方法参数)
         */
        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();

        Object target = point.getTarget();

        /**
         * 获得request一便获得cookie,(是spring的类)
         */
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = requestAttributes.getRequest();

        Cookie[] cookies = request.getCookies();

        //验证cookie中是否有token,判断是否登录
        for (Cookie cookie : cookies) {
           if(cookie.getName().equals("token")){
               String token = cookie.getValue();

               if (token.isEmpty()){
                   return "token为空";
               }else {//有cookie并且通过验证,执行本该执行的请求
                   Object invoke = null;
                   try {
                       /**
                        * 验证token逻辑
                        */

                       invoke = method.invoke(target, args[0]);//将args传入似乎也可以
                       return invoke; //返回执行结果
                   }catch (Exception e){
                       System.out.println(e.getMessage());
                   }
               }
           }
        }

//没有cookie就是没有登录
        return "没有token";
    }

}

controller层

	@NeedLogin
    @GetMapping("getAllUser")
    public List<User> getAllUser(){

        return userService.getAllUsers();//去数据库查询的值

    }

   @PassLogin
   @PostMapping("setUser")
   public User setUser(@RequestBody User user){
        return user;//将传的值返回,验证的,无意义
   }

使用spring的interceptor实现

创建拦截器类(这里我方便起见,使用一个类实现了拦截器接口HandlerInterceptor和注册拦截器接口WebMvcConfigurer,实际项目可以将他们分开分别实现)

/**
 * 使用sprinMVC的拦截器实现对请求的拦截
 */
@Component
public class ForVerifyInterceptor implements HandlerInterceptor, WebMvcConfigurer {

    /**
     * 实现的WebMvcConfigurer的方法
     * @param registry 将实现的interceptor注册,由于本类实现了两个接口所以就是this
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this).addPathPatterns("/**")//拦截所有请求
                .excludePathPatterns("/login");//除了‘/login’请求

    }

    /**
     * 实现HandleInterceptor方法,请求前拦截
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

            Cookie[] cookies = request.getCookies();

            for (Cookie cookie : cookies) {

                String name = cookie.getName();

                if (name.equals("token")) {

                    String value = cookie.getValue();

                    if (value.isEmpty()) {

                        retrunJson(response, "token为空");
                        return false;
                    } else {
                        /**
                         * 验证token的逻辑
                         */

                        return true;
                    }
                }
            }
            System.out.println(handler);
            retrunJson(response, "没有token");
            return false;
    }

    /**
     * 返回数据方法的包装
     * @param response
     * @param result
     */
    private void retrunJson(HttpServletResponse response, String result) {

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=UTF-8");
        PrintWriter writer = null;
        try {
             writer = response.getWriter();

             writer.write(result);
        }catch (Exception e){
            System.out.println(e);
        }finally {
            if (writer != null){
                writer.close();
            }
        }

    }

    /**
     * 实现HandleInterceptor方法,在视图渲染之前进行处理
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle");


    }

    /**
     * 实现HandleInterceptor方法,在这个视图渲染之后处理
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterComletion");
    }
}


使用注解方式进行请求路径验证(拦截所有请求,获得请求方法上的注解,验证方式与前面一样,二选其一即可)

/**
 * 使用sprinMVC的拦截器实现对请求的拦截
 */
@Component
public class ForVerifyInterceptor implements HandlerInterceptor, WebMvcConfigurer {

    /**
     * 实现的WebMvcConfigurer的方法
     * @param registry 将实现的interceptor注册,由于本类实现了两个接口所以就是this
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this).addPathPatterns("/**")//拦截所有请求
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");

        Class<? extends HttpServletRequest> clazz = request.getClass();

        Method method = clazz.getMethod(request.getMethod());

        boolean passLogin = method.isAnnotationPresent(PassLogin.class);

        if (passLogin) {//如果有PassLogin注解就可以直接跳过
            return true;
        }

        boolean needLogin = method.isAnnotationPresent(NeedLogin.class);

        if (needLogin) {
        //如果有NeedLogin则进行验证
        
        }
}


controller层就是根据请求路径拦截

  //不拦截
  @PostMapping("/login")
    public User getUser(@RequestBody User user){
       return user;
    }
//拦截
    @PostMapping("/getAllUser")
    public List<User> getUser(){
        return userService.getAllUsers();
    }

附jwt工具

引入第三工具(可以自造轮子,但没必要)

<dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.4.0</version>
</dependency>

生成token的方法(将用户的id加入到token中,使用用户密码类似加盐操作生成token)

public String getToken(User user) {
        String token="";
        token= JWT.create().withAudience(user.getId())
                .sign(Algorithm.HMAC256(user.getPassword()));
        return token;
    }

验证token方法

JWTVerifier jwtVerifier =JWT.require(Algorithm.HMAC256(user.getPassword())).build();
           try {
               jwtVerifier.verify(token);
           } catch (JWTVerificationException e) {
               throw new RuntimeException("401");
           }