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

共 7462字,需浏览 15分钟

 ·

2020-11-27 17:44

Spring Boot (v2.0.5.RELEASE)

  • 程序中有些资源(接口)是需要用户登录才能够使用的,或者是具有某种角色的用户(比如普通登录用户,或者系统管理员等)才能使用,本篇文章先为大家讲解如何控制使用某接口要求用户必须登录。

  • 实现的思路是

    1. 首先定义注解 @LoginUser,该注解用于标注哪些接口需要进行拦截

    2. 定义拦截器,拦截标注了 @LoginUser注解的接口

    3. 拦截之后判断该用户目前是不是处于登陆状态,如果是登陆状态则放行该请求,如果未登录则提示登陆

    4. 给方法或者类打上 @LoginUser注解进行测试


1. 定义标注注解 @LoginUser

  1. package com.futao.springmvcdemo.annotation;

  2. import com.futao.springmvcdemo.model.enums.Role;

  3. import java.lang.annotation.*;

  4. /**

  5. * @author futao

  6. * Created on 2018/9/19-14:39.

  7. * 登陆用户,用户角色

  8. */

  9. @Target(value = {

  10.        ElementType.METHOD,

  11.        ElementType.TYPE

  12. })

  13. @Retention(RetentionPolicy.RUNTIME)

  14. @Documented

  15. public @interface LoginUser {

  16.    /**

  17.     * 要求的用户角色

  18.     *

  19.     * @return

  20.     */

  21.    Role role() default Role.Normal;

  22. }


2. 定义拦截器 LoginUserInterceptor

  1. package com.futao.springmvcdemo.annotation.impl;

  2. import com.alibaba.fastjson.JSON;

  3. import com.futao.springmvcdemo.annotation.LoginUser;

  4. import com.futao.springmvcdemo.model.entity.constvar.ErrorMessage;

  5. import com.futao.springmvcdemo.model.system.RestResult;

  6. import com.futao.springmvcdemo.model.system.SystemConfig;

  7. import com.futao.springmvcdemo.utils.ThreadLocalUtils;

  8. import org.apache.commons.lang3.ObjectUtils;

  9. import org.slf4j.Logger;

  10. import org.slf4j.LoggerFactory;

  11. import org.springframework.stereotype.Component;

  12. import org.springframework.web.method.HandlerMethod;

  13. import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

  14. import javax.annotation.Resource;

  15. import javax.servlet.http.HttpServletRequest;

  16. import javax.servlet.http.HttpServletResponse;

  17. import javax.servlet.http.HttpSession;

  18. /**

  19. * @author futao

  20. * Created on 2018/9/19-14:44.

  21. * 对请求标记了LoginUser的方法进行拦截

  22. */

  23. @Component

  24. public class LoginUserInterceptor extends HandlerInterceptorAdapter {

  25.    private static final Logger logger = LoggerFactory.getLogger(LoginUserInterceptor.class);

  26.    @Resource

  27.    private ThreadLocalUtils<String> threadLocalUtils;

  28.    /**

  29.     * 在请求到达Controller之前进行拦截并处理

  30.     *

  31.     * @param request

  32.     * @param response

  33.     * @param handler

  34.     * @return

  35.     * @throws Exception

  36.     */

  37.    @Override

  38.    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

  39.        if (handler instanceof HandlerMethod) {

  40.            //注解在方法上

  41.            LoginUser loginUserAnnotation = ((HandlerMethod) handler).getMethodAnnotation(LoginUser.class);

  42.            //注解在类上

  43.            LoginUser classLoginUserAnnotation = ((HandlerMethod) handler).getMethod().getDeclaringClass().getAnnotation(LoginUser.class);

  44.            if (ObjectUtils.anyNotNull(loginUserAnnotation, classLoginUserAnnotation)) {

  45.                HttpSession session = request.getSession(false);

  46.                //session不为空

  47.                if (ObjectUtils.allNotNull(session)) {

  48.                    String loginUser = (String) session.getAttribute(SystemConfig.LOGIN_USER_SESSION_KEY);

  49.                    if (ObjectUtils.allNotNull(loginUser)) {

  50.                        System.out.println("当前登陆用户为:" + loginUser);

  51.                        //将当前用户的信息存入threadLocal中

  52.                        threadLocalUtils.set(loginUser);

  53.                    } else {

  54.                        System.out.println("用户不存在");

  55.                        return false;

  56.                    }

  57.                } else {//session为空,用户未登录

  58.                    RestResult restResult = new RestResult(false, "-1", ErrorMessage.NOT_LOGIN, ErrorMessage.NOT_LOGIN.substring(6));

  59.                    response.getWriter().append(JSON.toJSONString(restResult));

  60.                    return false;

  61.                }

  62.            }

  63.        }

  64.        return true;

  65.    }

  66.    @Override

  67.    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

  68.        //释放threadLocal资源

  69.        threadLocalUtils.remove();

  70.    }

  71. }


3. 注册拦截器

  1. package com.futao.springmvcdemo.annotation;

  2. import com.futao.springmvcdemo.annotation.impl.LoginUserInterceptor;

  3. import com.futao.springmvcdemo.annotation.impl.RequestLogInterceptor;

  4. import com.futao.springmvcdemo.annotation.impl.SignInterceptor;

  5. import org.springframework.boot.SpringBootConfiguration;

  6. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

  8. import javax.annotation.Resource;

  9. /**

  10. * @author futao

  11. * Created on 2018/9/18-15:15.

  12. */

  13. @SpringBootConfiguration

  14. public class WebMvcConfiguration implements WebMvcConfigurer {

  15.    @Resource

  16.    private SignInterceptor signInterceptor;

  17.    @Resource

  18.    private LoginUserInterceptor loginUserInterceptor;

  19.    @Resource

  20.    private RequestLogInterceptor requestLogInterceptor;

  21.    /**

  22.     * addInterceptor()的顺序需要严格按照程序的执行的顺序

  23.     *

  24.     * @param registry

  25.     */

  26.    @Override

  27.    public void addInterceptors(InterceptorRegistry registry) {

  28.        registry.addInterceptor(requestLogInterceptor).addPathPatterns("/**");

  29.        registry.addInterceptor(loginUserInterceptor).addPathPatterns("/**");

  30.        //  "/**"和"/*"是有区别的

  31.        registry.addInterceptor(signInterceptor).addPathPatterns("/**");

  32.    }

  33. }


4. 测试(可分别将注解打在类上和方法上进行测试)

  1. package com.futao.springmvcdemo.controller;

  2. import com.alibaba.fastjson.JSON;

  3. import com.alibaba.fastjson.JSONArray;

  4. import com.alibaba.fastjson.JSONObject;

  5. import com.futao.springmvcdemo.annotation.LoginUser;

  6. import com.futao.springmvcdemo.model.entity.User;

  7. import com.futao.springmvcdemo.model.system.SystemConfig;

  8. import com.futao.springmvcdemo.service.UserService;

  9. import org.apache.commons.lang3.ObjectUtils;

  10. import org.springframework.http.MediaType;

  11. import org.springframework.web.bind.annotation.*;

  12. import javax.annotation.Resource;

  13. import javax.servlet.http.HttpServletRequest;

  14. import javax.servlet.http.HttpSession;

  15. import java.util.List;

  16. import java.util.UUID;

  17. /**

  18. * @author futao

  19. * Created on 2018/9/19-15:05.

  20. */

  21. @RequestMapping(path = "User", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

  22. @RestController

  23. public class UserController {

  24.    @Resource

  25.    private UserService userService;

  26.    /**

  27.     * 获取当前的登陆的用户信息,其实是从threadLocal中获取

  28.     *

  29.     * @return

  30.     */

  31.    @LoginUser

  32.    @GetMapping(path = "my")

  33.    public JSONObject my() {

  34.        JSONObject jsonObject = new JSONObject();

  35.        jsonObject.put("当前的登陆的用户是:", userService.currentUser());

  36.        return jsonObject;

  37.    }

  38.    /**

  39.     * 模拟登陆接口

  40.     *

  41.     * @param mobile

  42.     * @param request

  43.     * @return

  44.     */

  45.    @PostMapping(path = "login")

  46.    public JSONObject login(

  47.            @RequestParam("mobile") String mobile,

  48.            HttpServletRequest request

  49.    ) {

  50.        HttpSession session = request.getSession();

  51.        session.setAttribute(SystemConfig.LOGIN_USER_SESSION_KEY, String.valueOf(UUID.randomUUID()));

  52.        session.setMaxInactiveInterval(SystemConfig.SESSION_INVALIDATE_SECOND);

  53.        return new JSONObject();

  54.    }

  55. }


4.1 测试未登录情况下调用标记了 @LoginUser的获取当前登陆用户信息接口 


4.2 登录 


4.3 登录之后调用调用标记了 @LoginUser的获取当前登陆用户信息接口 

稍微解释一下上面登陆和获取用户信息的逻辑: 用户请求登陆之后,会为该用户在系统中生成一个 HttpSession,同时在系统中有一个 Map来存放所有的 session信息,该 Map的 key为一个随机字符串, value为 session对象在系统中的堆地址,在登陆请求完成之后,系统会将该 sesion的 key值以 cookie(JSESSIONID)的形式写回浏览器。 用户下次登陆的时候,请求中会自动带上该 cookie,所以我们在标记了需要登陆的 @LoginUser注解的请求到达处理逻辑之前进行拦截,就是从 cookie中(JSESSIONID)取出 session的 key值,如果没有该 cookie,则代表用户没有登陆,如果有该 cookie,再在存放 cookie的 map中取,如果没有取到,则代表用户的 session已经过期了,需要重新登陆,或者 cookie是伪造的。 拿到了登陆用户的 session之后,我们去 Map中获取对应的值,一般是用户的 id,在通过这个用户 id,可以去数据库查该用户的信息,查到用户的信息之后将用户信息放入 threadLocal中,然后就可以在任何地方 get()到当前登陆的用户信息了,非常方便。

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



浏览 9
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报