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

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

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的获取当前登陆用户信息接口


image.png

4.2 登录


image.png

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


image.png


稍微解释一下上面登陆和获取用户信息的逻辑:

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


image.png


用户下次登陆的时候,请求中会自动带上该cookie,所以我们在标记了需要登陆的@LoginUser注解的请求到达处理逻辑之前进行拦截,就是从cookie中(JSESSIONID)取出sessionkey值,如果没有该cookie,则代表用户没有登陆,如果有该cookie,再在存放cookiemap中取,如果没有取到,则代表用户的session已经过期了,需要重新登陆,或者cookie是伪造的。

拿到了登陆用户的session之后,我们去Map中获取对应的值,一般是用户的id,在通过这个用户id,可以去数据库查该用户的信息,查到用户的信息之后将用户信息放入threadLocal中,然后就可以在任何地方get()到当前登陆的用户信息了,非常方便。

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

image.png

相关文章
|
12天前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
30 0
|
1月前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
19天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
42 4
SpringBoot必须掌握的常用注解!
|
21天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
70 2
|
21天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
34 1
|
1月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
16天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
12 0
|
1月前
|
XML Java 数据库
Spring boot的最全注解
Spring boot的最全注解
|
28天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
47 0