基于注解的用户权限拦截Spring HandlerInterceptor
Spring Boot (v2.0.5.RELEASE)
程序中有些资源(接口)是需要用户登录才能够使用的,或者是具有某种角色的用户(比如普通登录用户,或者系统管理员等)才能使用,本篇文章先为大家讲解如何控制使用某接口要求用户必须登录。
实现的思路是
首先定义注解
@LoginUser
,该注解用于标注哪些接口需要进行拦截定义拦截器,拦截标注了
@LoginUser
注解的接口拦截之后判断该用户目前是不是处于登陆状态,如果是登陆状态则放行该请求,如果未登录则提示登陆
给方法或者类打上
@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();
}
}
3. 注册拦截器
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("/**");
}
}
4. 测试(可分别将注解打在类上和方法上进行测试)
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();
}
}
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()
到当前登陆的用户信息了,非常方便。
使用上面的基于注解的拦截器可以实现很多功能,比如动态的第三方接口验签,和系统日志记录(不需要注解)等