一、日志记录
1、拦截器记录用户访问模块
例1:方法注解
(1)注解定义:
package com.demo.interceptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 声明注解作用于方法
@Target(ElementType.METHOD)
// 声明注解运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface LogModule {
String moduleCode();
}
(2)拦截器定义:
package com.demo.interceptor;
import com.demo.enums.LogModuleEnums;
import com.demo.service.LogModuleService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class LogModuleInterceptor implements HandlerInterceptor{
private LogModuleService logModuleService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//方法有无加注解
LogModule checkVer = method.getAnnotation(LogModule.class);
//1、没有加注解的接口直接放行
if( checkVer == null ){
return true;
}
// 2、加了注解,获取模块编码并记录
String moduleCode = checkVer.moduleCode();
BeanFactory factory = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
logModuleService = (LogModuleService) factory.getBean("logModuleService");
logModuleService.insertLogModuleRecord(moduleCode);
}
return true;
}
}
配置
package com.demo.config;
import com.demo.interceptor.LogModuleInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogModuleInterceptor());
}
}
(3)服务:
package com.demo.service.impl;
import com.demo.service.LogModuleService;
import org.springframework.stereotype.Service;
@Service("logModuleService")
public class LogModuleServiceImpl implements LogModuleService {
@Override
public void insertLogModuleRecord(String moduleCode) {
System.out.println("访问模块"+moduleCode);
}
}
(4)接口拦截注解:
package com.demo.controller;
import com.demo.dto.RoleDTO;
import com.demo.enums.LogModuleEnums;
import com.demo.interceptor.LogModule;
import com.demo.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequestMapping("/role")
@RestController
public class RoleController {
@Autowired
private RoleService roleService;
@RequestMapping("/listAllRoles")
@LogModule(moduleCode = "role")
public List<RoleDTO> listAllRoles(){
return roleService.listAllRoles();
}
@RequestMapping("/getRoleByRoleCode")
@LogModule(moduleCode = "role")
public RoleDTO getRoleByRoleCode(String roleCode){
return roleService.getRoleByRoleCode(roleCode);
}
}
package com.demo.controller;
import com.demo.dto.UserDTO;
import com.demo.interceptor.LogModule;
import com.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/listAllUsers")
@LogModule(moduleCode = "user")
public List<UserDTO> listAllUsers(){
return userService.listAllUsers();
}
@RequestMapping("/getUserByUserAccount")
@LogModule(moduleCode = "user")
public UserDTO getUserByUserAccount(String userAccount){
return userService.getUserByUserAccount(userAccount);
}
}
依次访问接口,后台打印:
例2:同时作用于类和方法注解:
(1)定义注解:
package com.demo.interceptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 声明注解作用于类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
// 声明注解运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface LogModule {
String moduleCode();
}
(2)拦截器:
package com.demo.interceptor;
import com.demo.enums.LogModuleEnums;
import com.demo.service.LogModuleService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class LogModuleInterceptor implements HandlerInterceptor{
private LogModuleService logModuleService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//方法有无加注解
LogModule annotation = method.getAnnotation(LogModule.class);
//1、没有加注解,判断所在类有没有加注解
if( annotation == null ){
System.out.println(method.getName()+"方法无注解");
annotation = method.getDeclaringClass().getAnnotation(LogModule.class);
if(annotation == null){
System.out.println(method.getName()+"所在类无注解");
return true;
}
System.out.println(method.getName()+"所在类有注解");
}
// 2、加了注解,获取模块编码并记录
String moduleCode = annotation.moduleCode();
BeanFactory factory = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
logModuleService = (LogModuleService) factory.getBean("logModuleService");
logModuleService.insertLogModuleRecord(moduleCode);
}
return true;
}
}
(3)接口注解情况:
package com.demo.controller;
import com.demo.dto.UserDTO;
import com.demo.interceptor.LogModule;
import com.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/listAllUsers")
public List<UserDTO> listAllUsers(){
return userService.listAllUsers();
}
@RequestMapping("/getUserByUserAccount")
@LogModule(moduleCode = "user")
public UserDTO getUserByUserAccount(String userAccount){
return userService.getUserByUserAccount(userAccount);
}
}
package com.demo.controller;
import com.demo.dto.RoleDTO;
import com.demo.enums.LogModuleEnums;
import com.demo.interceptor.LogModule;
import com.demo.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequestMapping("/role")
@RestController
@LogModule(moduleCode = "role")
public class RoleController {
@Autowired
private RoleService roleService;
@RequestMapping("/listAllRoles")
public List<RoleDTO> listAllRoles(){
return roleService.listAllRoles();
}
@RequestMapping("/getRoleByRoleCode")
public RoleDTO getRoleByRoleCode(String roleCode){
return roleService.getRoleByRoleCode(roleCode);
}
}
依次访问接口,后台打印:
例3:自定义注入枚举
同样同时作用于类和方法:
(1)定义枚举
package com.demo.enums;
public enum LogModuleEnums {
USER("user","用户"),
ROLE("role","角色");
public final String moduleCode;
public final String moduleName;
LogModuleEnums(String moduleCode,String moduleName){
this.moduleCode = moduleCode;
this.moduleName = moduleName;
}
}
(2)定义注解:
package com.demo.interceptor;
import com.demo.enums.LogModuleEnums;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 声明注解作用于类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
// 声明注解运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface LogModule {
LogModuleEnums module();
}
(3)拦截器修改:
LogModuleEnums module = annotation.module();
BeanFactory factory = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
logModuleService = (LogModuleService) factory.getBean("logModuleService");
logModuleService.insertLogModuleRecord(module);
(4)接口拦截:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/listAllUsers")
public List<UserDTO> listAllUsers(){
return userService.listAllUsers();
}
@RequestMapping("/getUserByUserAccount")
@LogModule(module = LogModuleEnums.USER)
public UserDTO getUserByUserAccount(String userAccount){
return userService.getUserByUserAccount(userAccount);
}
}
@RequestMapping("/role")
@RestController
@LogModule(module = LogModuleEnums.ROLE)
public class RoleController {
@Autowired
private RoleService roleService;
@RequestMapping("/listAllRoles")
public List<RoleDTO> listAllRoles(){
return roleService.listAllRoles();
}
@RequestMapping("/getRoleByRoleCode")
public RoleDTO getRoleByRoleCode(String roleCode){
return roleService.getRoleByRoleCode(roleCode);
}
}
(5)服务:
package com.demo.service.impl;
import com.demo.enums.LogModuleEnums;
import com.demo.service.LogModuleService;
import org.springframework.stereotype.Service;
@Service("logModuleService")
public class LogModuleServiceImpl implements LogModuleService {
@Override
public void insertLogModuleRecord(LogModuleEnums module) {
System.out.println("访问模块"+module.moduleCode+" "+module.moduleName);
}
}
依次访问接口,打印:
以上使用过滤器实现:
二、参数校验
1、拦截器对必须要传入token的接口做拦截
(1)自定义注解:
package com.demo.interceptor.tokencheck;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 声明注解作用于类、方法
@Target({ElementType.TYPE,ElementType.METHOD})
// 声明注解运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface TokenCheck {
}
(2)自定义拦截器:
package com.demo.interceptor.tokencheck;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
@Component
public class TokenCheckInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//方法有无加注解
TokenCheck annotation = method.getAnnotation(TokenCheck.class);
//1、没有加注解,判断所在类有没有加注解
if( annotation == null ){
annotation = method.getDeclaringClass().getAnnotation(TokenCheck.class);
if(annotation == null){
return true;
}
}
// 2、加了注解,校验请求头是否加了token
String token = request.getHeader("token");
if(StringUtils.isEmpty(token)){
System.out.println("头部未传入token");
return false;
}
}
return true;
}
}
(3)配置拦截器:
package com.demo.config;
import com.demo.interceptor.logmodule.LogModuleInterceptor;
import com.demo.interceptor.tokencheck.TokenCheckInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenCheckInterceptor());
registry.addInterceptor(new LogModuleInterceptor());
}
}
(4)接口加注解:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/listAllUsers")
@TokenCheck
public List<UserDTO> listAllUsers(){
return userService.listAllUsers();
}
@RequestMapping("/getUserByUserAccount")
@LogModule(module = LogModuleEnums.USER)
public UserDTO getUserByUserAccount(String userAccount){
return userService.getUserByUserAccount(userAccount);
}
}
@RequestMapping("/role")
@RestController
@LogModule(module = LogModuleEnums.ROLE)
@TokenCheck
public class RoleController {
@Autowired
private RoleService roleService;
@RequestMapping("/listAllRoles")
public List<RoleDTO> listAllRoles(){
return roleService.listAllRoles();
}
@RequestMapping("/getRoleByRoleCode")
public RoleDTO getRoleByRoleCode(String roleCode){
return roleService.getRoleByRoleCode(roleCode);
}
}
测试:
(1)访问http://localhost:7777/filterDemo/user/listAllUsers 不加token
后台打印:头部未传入token
加上token:
后台打印:token校验通过
(2)访问http://localhost:7777/filterDemo/user/getUserByUserAccount?userAccount=zhangsan
后台打印:无需校验token,不拦截
(3)访问/role下接口,情况同(1)。
2、参数校验
export function updateReport(report, emails, views) {
const id = report.id
const reportName = report.reportName
const safeLevel = report.safeLevel
const reportUrl = report.reportUrl
const remark = report.remark
const reportType = report.reportType
return request({
url: '/report/updateReport',
method: 'post',
data: {
id,
reportName,
reportUrl,
safeLevel,
remark,
reportType,
emails,
views
}
})
export function deleteReport(reportId) {
return request({
url: '/report/deleteReport',
method: 'post',
data: {
reportId
}
})
}
列表有查看、编辑、删除功能,前端判断该条数据的creator与当前登录用户是不是同一个人,如果是则展示编辑和删除按钮。为防止恶意攻击,可以在后端再加个拦截器,双重控制。
@Component
public class ReportHandler implements HandlerInterceptor{
@Autowired
private ReportService reportService;
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3)
throws Exception {
// TODO Auto-generated method stub
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
// TODO Auto-generated method stub
}
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse arg1,
Object arg2) throws Exception {
try{
String requestURI = req.getRequestURI();
//是删除或者编辑请求
if(requestURI.indexOf("updateReport")!=-1 || requestURI.indexOf("deleteReport")!=-1){
String reportId = null;
//删除的参数为reportId
if(req.getParameter("reportId") != null){
reportId = req.getParameter("reportId");
}
//编辑的参数为id
if(req.getParameter("id") != null){
reportId = req.getParameter("id");
}
Report report = reportService.getById(reportId);
User user = new SessionUtil().getCurrentUser();
if(!report.getCreator().equals(user.getId())){
return false;
}
}
return true;
}catch (Exception e) {
return true;
}
}
}
三、鉴权
1、垂直越权
判断用户是否有访问功能接口的角色或权限,下面用自定义拦截的方式解决垂直越权问题。
(1)自定义拦截
从接口中获取两个参数,菜单类型(菜单or按钮)、接口对应的权限编码,菜单类型默认为菜单,因为一个接口可能在多个菜单页面都有调用,所以这里权限编码是数组
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AuthVerify {
/**
* 操作类型,MenuTypeEnums
*/
MenuTypeEnums menuType() default MenuTypeEnums.MENU_TYPE_MENU;
/**
* 权限编码,MenuCodeEnums
*/
MenuCodeEnums[] authMenuCodes();
}
对应的枚举:
public enum MenuTypeEnums {
MENU_TYPE_MENU(1,"菜单"),
MENU_TYPE_ACTION(2,"按钮"),
SYSTEM(3,"系统资源")
;
public final Integer code;
public final String message;
MenuTypeEnums(int code, String message ) {
this.code = code;
this.message = message;
}
}
public enum MenuCodeEnums {
USER_MANAGE("web:userManage","用户管理"),
USER_MANAGE_ADD("web:userManage:add","新增用户")
;
public final String code;
public final String name;
MenuCodeEnums(String code, String name) {
this.code = code;
this.name = name;
}
}
(2)业务接口注解:
@PostMapping(value = "/user/getPageList")
@AuthVerify(authMenuCodes = {MenuCodeEnums.USER_MANAGE,MenuCodeEnums.USER_MANAGE_ADD})
public ResponseMessage getPageList(){
//
}
@GetMapping("/listPositionByName")
@AuthVerify(menuType = MenuTypeEnums.SYSTEM,authMenuCodes = {})
public ResponseMessage listPositionByName(@RequestParam(value = "positionName", required = false) String positionName){
//
}
(3)拦截判断:
@Component
public class AuthCheckInterceptor implements HandlerInterceptor {
private static Logger logger = LoggerFactory.getLogger(HandlerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//方法有无加注解
AuthVerify annotation = method.getAnnotation(AuthVerify.class);
if (annotation == null){
return true;
}
//有注解
//1、资源类型是否是系统,是系统均有查看权限
Integer authMenuType = annotation.menuType().code;
if(MenuTypeEnums.SYSTEM.code.equals(authMenuType)){
logger.info("AuthCheckInterceptor.preHandle 接口资源类型无需校验,放行");
return true;
}
//2、资源类型是普通菜单/按钮,控制访问权限
MenuCodeEnums[] authMenuCodeEnums = annotation.authMenuCodes();
JwtUser jwtUser = (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String userName = jwtUser.getUsername();
List<MenuVO> userMenus = jwtUser.getMenuVOList();
List<String> userMenuCodes = userMenus.stream().map(MenuVO::getMenuCode).collect(Collectors.toList());
for(MenuCodeEnums menuCodeEnum : authMenuCodeEnums){
String authMenuCode = menuCodeEnum.menuCode;
if(userMenuCodes.contains(authMenuCode)){
return true;
}
}
//处理请求失败返回
logger.warn("AuthCheckInterceptor.preHandle,userName={}无权限访问",userName);
setErrorRes(response);
return false;
}
} catch (Exception e) {
logger.error("AuthCheckInterceptor.preHandle异常", e);
return true;
}
return true;
}
private void setErrorRes(HttpServletResponse response) {
try{
ResponseMessage responseMessage = ResponseMessage.error("没有权限访问");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(responseMessage));
}catch (Exception e){
logger.error("AuthCheckInterceptor.preHandle设置自定义返回异常",e);
}
}
}
拦截通过则正常返回数据,拦截校验失败返回错误信息:
{"code":400,"message":"没有权限访问","status":"error","success":false}
(4)改进
因只有部分接口需要权限拦截校验,使用拦截器的话大部分接口都是走到无注解这步,可以采用AOP切面,只拦截注解的接口。
<!-- aop和aspect -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Aspect
@Component
public class AuthCheckAspect {
private static Logger logger = LoggerFactory.getLogger(AuthCheckAspect.class);
@Around("@annotation(com.demo.common.AuthVerify)")
public Object doAuthFilter(ProceedingJoinPoint pjp) throws Throwable {
try{
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method targetMethod = methodSignature.getMethod();
AuthVerify annotation = targetMethod.getAnnotation(AuthVerify.class);
//非AuthVerify权限类注解,放开
if (annotation == null) {
return pjp.proceed();
}
//有注解
//1、资源类型是否是系统,是系统均有查看权限
Integer authMenuType = annotation.menuType().code;
if(MenuTypeEnums.SYSTEM.code.equals(authMenuType)){
logger.info("AuthCheckAspect.doAuthFilter 接口资源类型无需校验,放行");
return pjp.proceed();
}
//2、资源类型是普通菜单/按钮,控制访问权限
MenuCodeEnums[] authMenuCodeEnums = annotation.authMenuCodes();
JwtUser jwtUser = (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String userName = jwtUser.getUsername();
List<MenuVO> userMenus = jwtUser.getMenuVOList();
List<String> userMenuCodes = userMenus.stream().map(MenuVO::getMenuCode).collect(Collectors.toList());
for(MenuCodeEnums menuCodeEnum : authMenuCodeEnums){
String authMenuCode = menuCodeEnum.menuCode;
if(userMenuCodes.contains(authMenuCode)){
return pjp.proceed();
}
}
//请求失败
logger.warn("AuthCheckAspect.doAuthFilter,userName={}无权限访问",userName);
return ResponseMessage.error("没有权限访问");
}catch (Exception e){
return pjp.proceed();
}
}
}
四、参数获取
1、获取@RequestBody封装的参数
controller接口接收参数,@RequestBody注解的与@RequestParam(缺省)注解的,下面来看下这两种方式对于拦截器和过滤器的差别:
1.1、不带@RequestBody注解的情况:
(1)pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>myDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
(2)dto:
package com.demo.dto;
public class User {
private Integer id;
private String userName;
private Integer age;
//省略set、get方法
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
(3)controller:
package com.demo.controller;
import com.demo.dto.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/test")
public void test(User user){
System.out.println("接口:"+user);
}
}
(4)拦截器:
package com.demo.intercepter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class ActionHandle implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
try {
String requestUrl = httpServletRequest.getRequestURI();
Map<String, String[]> originRequestMap = httpServletRequest.getParameterMap();
Map<String,String> requestMap = new HashMap<String,String>();
for (String key : originRequestMap.keySet()) {
String[] values = originRequestMap.get(key);
requestMap.put(key,values[0]);
}
System.out.println("拦截器:"+requestMap);
} catch (Exception e) {
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
package com.demo.intercepter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@SpringBootConfiguration
public class MySpringMVCConfig extends WebMvcConfigurerAdapter {
@Autowired
private ActionHandle myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/**");
}
}
(5)过滤器:
package com.demo.filter;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Map<String, String[]> originRequestMap = request.getParameterMap();
Map<String,String> requestMap = new HashMap<String,String>();
for (String key : originRequestMap.keySet()) {
String[] values = originRequestMap.get(key);
requestMap.put(key,values[0]);
}
System.out.println("过滤器:"+requestMap);
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
测试:访问:localhost:9999/test/user/test?id=1&userName=张三&age=6,后台打印:
1.2、@RequestBody注解
在controller中加一个接口
@RequestMapping("/test1")
public void test1( @RequestBody User user){
System.out.println("接口:"+user);
}
后台打印:
原因分析:普通的参数可以从request的getParameterMap中获取,而@RequestBody的参数需要从request的InputStream中获取。但是InputStream只能读取一次,如果过滤器读取了参数,后面拦截器和controler层就读取不到参数了,所以这类参数需要单独获取,可以把request封装一下,copy一份requet,一个用于在拦截器(过滤器)中读取参数,一个放行给controller使用。
解决方法:改造下代码:
(1)pom加入以下依赖:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
(2)新增两个util:这是关键,复制input流
package com.demo.util;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.net.URLDecoder;
import java.util.*;
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> paramsMap;
@Override
public Map getParameterMap() {
return paramsMap;
}
@Override
public String getParameter(String name) {
String[] values = paramsMap.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {
return paramsMap.get(name);
}
@Override
public Enumeration getParameterNames() {
return Collections.enumeration(paramsMap.keySet());
}
private String getRequestBody(InputStream stream) {
String line = "";
StringBuilder body = new StringBuilder();
int counter = 0;
// 读取POST提交的数据内容
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
try {
while ((line = reader.readLine()) != null) {
if (counter > 0) {
body.append("rn");
}
body.append(line);
counter++;
}
} catch (IOException e) {
e.printStackTrace();
}
return body.toString();
}
private HashMap<String, String[]> getParamMapFromPost(HttpServletRequest request) {
String body = "";
try {
body = getRequestBody(request.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
HashMap<String, String[]> result = new HashMap<String, String[]>();
if (null == body || 0 == body.length()) {
return result;
}
return parseQueryString(body);
}
// 自定义解码函数
private String decodeValue(String value) {
if (value.contains("%u")) {
return Encodes.urlDecode(value);
} else {
try {
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException e) {
// 非UTF-8编码
return "";
}
}
}
public HashMap<String, String[]> parseQueryString(String s) {
String valArray[] = null;
if (s == null) {
throw new IllegalArgumentException();
}
HashMap<String, String[]> ht = new HashMap<String, String[]>();
StringTokenizer st = new StringTokenizer(s, "&");
while (st.hasMoreTokens()) {
String pair = (String) st.nextToken();
int pos = pair.indexOf('=');
if (pos == -1) {
continue;
}
String key = pair.substring(0, pos);
String val = pair.substring(pos + 1, pair.length());
if (ht.containsKey(key)) {
String oldVals[] = (String[]) ht.get(key);
valArray = new String[oldVals.length + 1];
for (int i = 0; i < oldVals.length; i++) {
valArray[i] = oldVals[i];
}
valArray[oldVals.length] = decodeValue(val);
} else {
valArray = new String[1];
valArray[0] = decodeValue(val);
}
ht.put(key, valArray);
}
return ht;
}
private Map<String, String[]> getParamMapFromGet(HttpServletRequest request) {
return parseQueryString(request.getQueryString());
}
public String getBody(){
return new String(body);
}
// 报文
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = readBytes(request.getInputStream());
//
/*if ("POST".equals(request.getMethod().toUpperCase())) {
paramsMap = getParamMapFromPost(this);
} else {
paramsMap = getParamMapFromGet(this);
}*/
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener arg0) {
}
};
}
private static byte[] readBytes(InputStream in) throws IOException {
BufferedInputStream bufin = new BufferedInputStream(in);
int buffSize = 1024;
ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);
byte[] temp = new byte[buffSize];
int size = 0;
while ((size = bufin.read(temp)) != -1) {
out.write(temp, 0, size);
}
bufin.close();
byte[] content = out.toByteArray();
return content;
}
}
package com.demo.util;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public class Encodes {
private static final String DEFAULT_URL_ENCODING = "UTF-8";
private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
/**
* Hex编码.
*/
public static String encodeHex(byte[] input) {
return Hex.encodeHexString(input);
}
/**
* Hex解码.
*/
public static byte[] decodeHex(String input) {
try {
return Hex.decodeHex(input.toCharArray());
} catch (DecoderException e) {
return null;
}
}
/**
* Base64编码.
*/
public static String encodeBase64(byte[] input) {
return Base64.encodeBase64String(input);
}
/**
* Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548).
*/
public static String encodeUrlSafeBase64(byte[] input) {
return Base64.encodeBase64URLSafeString(input);
}
/**
* Base64解码.
*/
public static byte[] decodeBase64(String input) {
return Base64.decodeBase64(input);
}
/**
* Base62编码。
*/
public static String encodeBase62(byte[] input) {
char[] chars = new char[input.length];
for (int i = 0; i < input.length; i++) {
chars[i] = BASE62[(input[i] & 0xFF) % BASE62.length];
}
return new String(chars);
}
/**
* Html 转码.
*/
public static String escapeHtml(String html) {
return StringEscapeUtils.escapeHtml4(html);
}
/**
* Html 解码.
*/
public static String unescapeHtml(String htmlEscaped) {
return StringEscapeUtils.unescapeHtml4(htmlEscaped);
}
/**
* Xml 转码.
*/
public static String escapeXml(String xml) {
return StringEscapeUtils.escapeXml(xml);
}
/**
* Xml 解码.
*/
public static String unescapeXml(String xmlEscaped) {
return StringEscapeUtils.unescapeXml(xmlEscaped);
}
/**
* URL 编码, Encode默认为UTF-8.
*/
public static String urlEncode(String part) {
try {
return URLEncoder.encode(part, DEFAULT_URL_ENCODING);
} catch (UnsupportedEncodingException e) {
//throw Exceptions.unchecked(e);
return null;
}
}
/**
* URL 解码, Encode默认为UTF-8.
*/
public static String urlDecode(String part) {
try {
return URLDecoder.decode(part, DEFAULT_URL_ENCODING);
} catch (UnsupportedEncodingException e) {
//throw Exceptions.unchecked(e);
return null;
}
}
}
(3)改造拦截器的获取参数部分:
package com.demo.intercepter;
import com.alibaba.fastjson.JSON;
import com.demo.util.BodyReaderHttpServletRequestWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@Component
public class ActionHandle implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
try {
String requestUrl = httpServletRequest.getRequestURI();
ServletRequest requestWrapper = null;
String param = "";
String method = httpServletRequest.getMethod().toUpperCase();
String type = httpServletRequest.getContentType();
if ("POST".equals(method)
&& "application/json".equalsIgnoreCase(type)) {
requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
}
if (requestWrapper == null) {
Map<String, String[]> originRequestMap = httpServletRequest.getParameterMap();
Map<String,String> requestMap = new HashMap<String,String>();
for (String key : originRequestMap.keySet()) {
String[] values = originRequestMap.get(key);
requestMap.put(key,values[0]);
}
param = JSON.toJSONString(requestMap);
} else {
param = ((BodyReaderHttpServletRequestWrapper) requestWrapper).getBody();
}
System.out.println("拦截器:"+param);
} catch (Exception e) {
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
(4)改造过滤器的获取参数部分:
package com.demo.filter;
import com.alibaba.fastjson.JSON;
import com.demo.util.BodyReaderHttpServletRequestWrapper;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
String param = "";
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String method = httpServletRequest.getMethod().toUpperCase();
String type = httpServletRequest.getContentType();
if ("POST".equals(method)
&& "application/json".equalsIgnoreCase(type)) {
requestWrapper = new BodyReaderHttpServletRequestWrapper(
(HttpServletRequest) request);
}
}
if (requestWrapper == null) {
Map<String, String[]> originRequestMap = request.getParameterMap();
Map<String,String> requestMap = new HashMap<String,String>();
for (String key : originRequestMap.keySet()) {
String[] values = originRequestMap.get(key);
requestMap.put(key,values[0]);
}
param = JSON.toJSONString(requestMap);
} else {
param = ((BodyReaderHttpServletRequestWrapper) requestWrapper).getBody();
}
System.out.println("过滤器:"+param);
//放行
if (requestWrapper == null) {
chain.doFilter(request, response);
}else{
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
测试:
1)带@RequestBody注解的请求:
2)不带@RequestBody注解的请求: