SpringMVC拦截器实现实训项目权限统一的校验
一、SpringMVC拦截器配置及类初始化
1.1 SpringMVC的配置文件dispatcher-servlet.xml
在dispatcher-servlet.xml中定义拦截器,如果是manage/a.do,则需要定义path为/manage/*,如果是manage/product/a.do,则需要定义path为/manage/**,再使用bean标签,让我们的拦截器指定到AuthorityInterceptor上
<mvc:interceptors>
<!--定义在这里的,所有都会拦截-->
<mvc:interceptor>
<mvc:mapping path="/manage/**"/>
<bean class="cn.lnsf.controller.common.interceptor.AuthorityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
1.2 AuthorityInterceptor.java
需要实现HandlerInterceptor接口,其中有3个方法,preHandle、postHandle、afterCompletion,当preHandle返回值为true后,便会进入到controller层,controller执行完成后就会进入到postHandle,再到afterCompletion
@Slf4j
public class AuthorityInterceptor implements HandlerInterceptor {
//注重于preHandle,在用户进入controller的*.do之前判断其权限
//return false的话就不会进入到controller里面
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
log.info("preHandle");
//return false;
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
log.info("postHandle");
}
//在所有处理完成后调用的,如果我们项目不是前后端分离的,
//需要返回一个视图的时候,就在视图呈现之后调用afterCompletion
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
log.info("afterCompletion");
}
}
二、拦截器参数解析及登录信息获取,重置Response
2.1 AuthorityInterceptor.java
@Slf4j
public class AuthorityInterceptor implements HandlerInterceptor {
//注重于preHandle,在用户进入controller的*.do之前判断其权限
//return false的话就不会进入到controller里面
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
log.info("preHandle");
//请求中Controller中的方法名
HandlerMethod handlerMethod = (HandlerMethod)o;
//解析HandlerMethod
//例如:login
String methodName = handlerMethod.getMethod().getName();
//getSimpleName就是类名,name则还包含包名
//例如:UserManageController
String className = handlerMethod.getBean().getClass().getSimpleName();
//解析参数,具体的参数key-value
StringBuffer requestParamBuffer = new StringBuffer();
Map paramMap = httpServletRequest.getParameterMap();
//迭代
Iterator it = paramMap.entrySet().iterator();
while(it.hasNext()){
//Map.Entry更方便输出
Map.Entry entry = (Map.Entry) it.next();
String mapKey = (String) entry.getKey();
String mapValue = StringUtils.EMPTY;
//request这个参数的map,里面的value返回的是一个string数组
Object object = entry.getValue();
if(object instanceof String[]){
String[] strs = (String[])object;
mapValue = Arrays.toString(strs);
}
//用于打印日志
requestParamBuffer.append(mapKey).append("=").append(mapValue);
}
//判断
User user = null;
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isNotEmpty(loginToken)){
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
user = JsonUtil.string2Obj(userJsonStr,User.class);
}
if(user==null || (user.getRole().intValue()!= Const.Role.ROLE_ADMIN)){
//返回false,即不会调用controller里面的方法
//由于需要修改返回值,需要重置返回值后面使用getWriter才不会出现异常
//这里相当于将response托管到拦截器当中,则需要重新设置属性
httpServletResponse.reset();
//设置编码
httpServletResponse.setCharacterEncoding("UTF-8");
//设置返回类型
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter out = httpServletResponse.getWriter();
if(user == null){
out.println(JsonUtil.obj2String(ServerResponse.createByErrorMessage("拦截器拦截,用户未登录")));
}else{
out.println(JsonUtil.obj2String(ServerResponse.createByErrorMessage("拦截器拦截,用户权限不足")));
}
//将流清空,再关闭
out.flush();
out.close();
//不进入controller
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
log.info("postHandle");
}
//在所有处理完成后调用的,如果我们项目不是前后端分离的,
//需要返回一个视图的时候,就在视图呈现之后调用afterCompletion
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
log.info("afterCompletion");
}
}
2.2 测试结果
三、拦截管理员登录循环问题
3.1 拦截器产生的问题
如果login.do也是在manager/controller里面,但是用户在初次登录的时候拦截器的user本来就为null,所以请求不到login.do,这时候拦截器便让登录方法进入死循环
3.2 解决方法一,修改dispatcher-servlet.xml
<mvc:interceptors>
<!--定义在这里的,所有都会拦截-->
<mvc:interceptor>
<mvc:mapping path="/manage/**"/>
<!--不拦截login.do-->
<mvc:exclude-mapping path="/manage/user/login.do"/>
<bean class="cn.lnsf.controller.common.interceptor.AuthorityInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
3.3 解决方法二,修改拦截器(推荐)
由于可以通过在handler中获取到方法的名字以及类名,则通过判断是否为UserAdminController下的login的方法,如果是则不打印日志以及return true让方法进入controller
String methodName = handlerMethod.getMethod().getName();
String className = handlerMethod.getBean().getClass().getSimpleName();
//判断方法
if(StringUtils.equals(className,"UserAdminController")&&StringUtils.equals(methodName,"login")){
log.info("拦截器拦截到请求,className:{},methodName:{}",className,methodName);
//不打印参数,因为参数里面有账号密码
return true;
}
四、重构代码例子
4.1 管理员管理商品分类CategoryAdminController
初期代码中,如果管理员需要添加商品分类的话,/manage/category/下的add_category.do会从cookie中获取信息,进行判断,再进行权限判断
//添加分类
@RequestMapping("add_category.do")
@ResponseBody
public ServerResponse addCategory(HttpServletRequest httpServletRequest,String categoryName,@RequestParam(value = "parentId",defaultValue = "0") int parentId){
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr,User.class);
if(user==null){
return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"用户未登录,请登录");
}
if(iUserService.checkAdminRole(user).isSuccess()){
return iCategoryService.addCategory(categoryName,parentId);
}
return ServerResponse.createByErrorMessage("非管理员,无权限操作");
}
//修改分类名字
@RequestMapping("update_category_name.do")
@ResponseBody
public ServerResponse setCategoryName(HttpServletRequest httpServletRequest,Integer categoryId,String categoryName){
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr,User.class);
if(user==null){
return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"用户未登录,请登录");
}
if(iUserService.checkAdminRole(user).isSuccess()){
return iCategoryService.updateCategoryName(categoryId,categoryName);
}
return ServerResponse.createByErrorMessage("非管理员,无权限操作");
}
//深度搜索
@RequestMapping("deep_category.do")
@ResponseBody
public ServerResponse getCategoryAndDeepChildrenCategory(HttpServletRequest httpServletRequest, @RequestParam(value="categoryId",defaultValue = "0")int categoryId){
String loginToken = CookieUtil.readLoginToken(httpServletRequest);
if(StringUtils.isEmpty(loginToken)){
return ServerResponse.createByErrorMessage("用户未登录,无法获取当前用户的信息");
}
String userJsonStr = RedisShardedPoolUtil.get(loginToken);
User user = JsonUtil.string2Obj(userJsonStr,User.class);
if(user==null){
return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),"用户未登录,请登录");
}
if(iUserService.checkAdminRole(user).isSuccess()){
//当前节点id和递归子节点id
return iCategoryService.selectCategoryAndChildrenById(categoryId);
}
return ServerResponse.createByErrorMessage("非管理员,无权限操作");
}
由于拦截器的作用包括了判断是否登录以及权限的问题,所以可以将添加、深搜、修改等代码改下如下,代码简洁明了
//添加商品分类
@RequestMapping("add_category.do")
@ResponseBody
public ServerResponse addCategory(HttpServletRequest httpServletRequest,String categoryName,@RequestParam(value = "parentId",defaultValue = "0") int parentId){
return iCategoryService.addCategory(categoryName,parentId);
}
//修改分类名字
@RequestMapping("update_category_name.do")
@ResponseBody
public ServerResponse setCategoryName(HttpServletRequest httpServletRequest,Integer categoryId,String categoryName){
return iCategoryService.updateCategoryName(categoryId,categoryName);
}
//深度搜索
@RequestMapping("deep_category.do")
@ResponseBody
public ServerResponse getCategoryAndDeepChildrenCategory(HttpServletRequest httpServletRequest, @RequestParam(value="categoryId",defaultValue = "0")int categoryId){
return iCategoryService.selectCategoryAndChildrenById(categoryId);
}