基于注解的用户权限拦截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()到当前登陆的用户信息了,非常方便。

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



浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

分享
举报