天天看点

基于注解的用户权限拦截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

    的获取当前登陆用户信息接口
    基于注解的用户权限拦截Spring HandlerInterceptor

    未登录

    4.2 登录

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

    登录操作

    4.3 登录之后调用调用标记了

    @LoginUser

    基于注解的用户权限拦截Spring HandlerInterceptor
    登陆之后

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

用户请求登陆之后,会为该用户在系统中生成一个

HttpSession

,同时在系统中有一个

Map

来存放所有的

session

信息,该

Map

key

为一个随机字符串,

value

session

对象在系统中的堆地址,在登陆请求完成之后,系统会将该

sesion

key

值以

cookie

(JSESSIONID)的形式写回浏览器。

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

设置cookie

用户下次登陆的时候,请求中会自动带上该

cookie

,所以我们在标记了需要登陆的

@LoginUser

注解的请求到达处理逻辑之前进行拦截,就是从

cookie

中(JSESSIONID)取出

session

key

值,如果没有该

cookie

,则代表用户没有登陆,如果有该

cookie

,再在存放

cookie

map

中取,如果没有取到,则代表用户的

session

已经过期了,需要重新登陆,或者

cookie

是伪造的。

拿到了登陆用户的

session

之后,我们去

Map

中获取对应的值,一般是用户的

id

,在通过这个用户

id

,可以去数据库查该用户的信息,查到用户的信息之后将用户信息放入

threadLocal

中,然后就可以在任何地方

get()

到当前登陆的用户信息了,非常方便。

使用上面的基于注解的拦截器可以实现很多功能,比如

动态的第三方接口验签

,和系统日志记录(不需要注解)等

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

日志系统