美文网首页Java 杂谈Spring-Boot程序员
基于注解的用户权限拦截Spring HandlerInterce

基于注解的用户权限拦截Spring HandlerInterce

作者: 垃圾简书_吃枣药丸 | 来源:发表于2018-09-23 01:33 被阅读221次

    Spring Boot (v2.0.5.RELEASE)

    • 程序中有些资源(接口)是需要用户登录才能够使用的,或者是具有某种角色的用户(比如普通登录用户,或者系统管理员等)才能使用,本篇文章先为大家讲解如何控制使用某接口要求用户必须登录。
    • 实现的思路是
      1. 首先定义注解@LoginUser,该注解用于标注哪些接口需要进行拦截
      2. 定义拦截器,拦截标注了@LoginUser注解的接口
      3. 拦截之后判断该用户目前是不是处于登陆状态,如果是登陆状态则放行该请求,如果未登录则提示登陆
      4. 给方法或者类打上@LoginUser注解进行测试
    1. 定义标注注解@LoginUser
    package com.futao.springmvcdemo.annotation;
    
    import com.futao.springmvcdemo.model.enums.Role;
    
    import java.lang.annotation.*;
    
    /**
     * @author futao
     * Created on 2018/9/19-14:39.
     * 登陆用户,用户角色
     */
    @Target(value = {
            ElementType.METHOD,
            ElementType.TYPE
    })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface LoginUser {
        /**
         * 要求的用户角色
         *
         * @return
         */
        Role role() default Role.Normal;
    }
    

    2。 定义拦截器LoginUserInterceptor

    package com.futao.springmvcdemo.annotation.impl;
    
    import com.alibaba.fastjson.JSON;
    import com.futao.springmvcdemo.annotation.LoginUser;
    import com.futao.springmvcdemo.model.entity.constvar.ErrorMessage;
    import com.futao.springmvcdemo.model.system.RestResult;
    import com.futao.springmvcdemo.model.system.SystemConfig;
    import com.futao.springmvcdemo.utils.ThreadLocalUtils;
    import org.apache.commons.lang3.ObjectUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    /**
     * @author futao
     * Created on 2018/9/19-14:44.
     * 对请求标记了LoginUser的方法进行拦截
     */
    @Component
    public class LoginUserInterceptor extends HandlerInterceptorAdapter {
    
        private static final Logger logger = LoggerFactory.getLogger(LoginUserInterceptor.class);
    
        @Resource
        private ThreadLocalUtils<String> threadLocalUtils;
    
        /**
         * 在请求到达Controller之前进行拦截并处理
         *
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            if (handler instanceof HandlerMethod) {
                //注解在方法上
                LoginUser loginUserAnnotation = ((HandlerMethod) handler).getMethodAnnotation(LoginUser.class);
                //注解在类上
                LoginUser classLoginUserAnnotation = ((HandlerMethod) handler).getMethod().getDeclaringClass().getAnnotation(LoginUser.class);
                if (ObjectUtils.anyNotNull(loginUserAnnotation, classLoginUserAnnotation)) {
                    HttpSession session = request.getSession(false);
                    //session不为空
                    if (ObjectUtils.allNotNull(session)) {
                        String loginUser = (String) session.getAttribute(SystemConfig.LOGIN_USER_SESSION_KEY);
                        if (ObjectUtils.allNotNull(loginUser)) {
                            System.out.println("当前登陆用户为:" + loginUser);
                            //将当前用户的信息存入threadLocal中
                            threadLocalUtils.set(loginUser);
                        } else {
                            System.out.println("用户不存在");
                            return false;
                        }
                    } else {//session为空,用户未登录
                        RestResult restResult = new RestResult(false, "-1", ErrorMessage.NOT_LOGIN, ErrorMessage.NOT_LOGIN.substring(6));
                        response.getWriter().append(JSON.toJSONString(restResult));
                        return false;
                    }
                }
            }
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            //释放threadLocal资源
            threadLocalUtils.remove();
        }
    }
    
    1. 注册拦截器
    package com.futao.springmvcdemo.annotation;
    
    import com.futao.springmvcdemo.annotation.impl.LoginUserInterceptor;
    import com.futao.springmvcdemo.annotation.impl.RequestLogInterceptor;
    import com.futao.springmvcdemo.annotation.impl.SignInterceptor;
    import org.springframework.boot.SpringBootConfiguration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    import javax.annotation.Resource;
    
    /**
     * @author futao
     * Created on 2018/9/18-15:15.
     */
    @SpringBootConfiguration
    public class WebMvcConfiguration implements WebMvcConfigurer {
        @Resource
        private SignInterceptor signInterceptor;
        @Resource
        private LoginUserInterceptor loginUserInterceptor;
        @Resource
        private RequestLogInterceptor requestLogInterceptor;
    
        /**
         * addInterceptor()的顺序需要严格按照程序的执行的顺序
         *
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(requestLogInterceptor).addPathPatterns("/**");
            registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");
            //  "/**"和"/*"是有区别的
            registry.addInterceptor(signInterceptor).addPathPatterns("/**");
        }
    }
    
    1. 测试(可分别将注解打在类上和方法上进行测试)
    package com.futao.springmvcdemo.controller;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.futao.springmvcdemo.annotation.LoginUser;
    import com.futao.springmvcdemo.model.entity.User;
    import com.futao.springmvcdemo.model.system.SystemConfig;
    import com.futao.springmvcdemo.service.UserService;
    import org.apache.commons.lang3.ObjectUtils;
    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.*;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import java.util.List;
    import java.util.UUID;
    
    /**
     * @author futao
     * Created on 2018/9/19-15:05.
     */
    @RequestMapping(path = "User", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @RestController
    public class UserController {
    
        @Resource
        private UserService userService;
    
        /**
         * 获取当前的登陆的用户信息,其实是从threadLocal中获取
         *
         * @return
         */
        @LoginUser
        @GetMapping(path = "my")
        public JSONObject my() {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("当前的登陆的用户是:", userService.currentUser());
            return jsonObject;
        }
    
        /**
         * 模拟登陆接口
         *
         * @param mobile
         * @param request
         * @return
         */
        @PostMapping(path = "login")
        public JSONObject login(
                @RequestParam("mobile") String mobile,
                HttpServletRequest request
        ) {
            HttpSession session = request.getSession();
            session.setAttribute(SystemConfig.LOGIN_USER_SESSION_KEY, String.valueOf(UUID.randomUUID()));
            session.setMaxInactiveInterval(SystemConfig.SESSION_INVALIDATE_SECOND);
            return new JSONObject();
        }
    }
    
    1. 测试
      4.1 未登录情况下调用标记了@LoginUser的获取当前登陆用户信息接口
      未登录
      4.2 登录
      登录操作
      4.3 登录之后调用调用标记了@LoginUser的获取当前登陆用户信息接口
      登陆之后

    稍微解释一下上面登陆和获取用户信息的逻辑:
    用户请求登陆之后,会为该用户在系统中生成一个HttpSession,同时在系统中有一个Map来存放所有的session信息,该Mapkey为一个随机字符串,valuesession对象在系统中的堆地址,在登陆请求完成之后,系统会将该sesionkey值以cookie(JSESSIONID)的形式写回浏览器。

    设置cookie
    用户下次登陆的时候,请求中会自动带上该cookie,所以我们在标记了需要登陆的@LoginUser注解的请求到达处理逻辑之前进行拦截,就是从cookie中(JSESSIONID)取出sessionkey值,如果没有该cookie,则代表用户没有登陆,如果有该cookie,再在存放cookiemap中取,如果没有取到,则代表用户的session已经过期了,需要重新登陆,或者cookie是伪造的。
    拿到了登陆用户的session之后,我们去Map中获取对应的值,一般是用户的id,在通过这个用户id,可以去数据库查该用户的信息,查到用户的信息之后将用户信息放入threadLocal中,然后就可以在任何地方get()到当前登陆的用户信息了,非常方便。

    使用上面的基于注解的拦截器可以实现很多功能,比如动态的第三方接口验签,和系统日志记录(不需要注解)等

    日志系统

    相关文章

      网友评论

        本文标题:基于注解的用户权限拦截Spring HandlerInterce

        本文链接:https://www.haomeiwen.com/subject/uphfoftx.html