引入:https://blog.csdn.net/catoop/article/details/69210140
本文基于Shiro权限注解方式来控制Controller方法是否能够访问。
例如使用到注解: @RequiresPermissions
来控制是否有对应权限才可以访问 @RequiresUser
来控制是否存在用户登录状态才可以访问
想了解Shiro是如何通过注解来控制权限的,可以查看源码 AopAllianceAnnotationsAuthorizingMethodInterceptor
,其构造方法中添加了几个对应的权限注解方法拦截器(这里不做详细阐述)。
用户在请求使用这些注解方式控制的方法时,如果没有通过权限校验。Shiro 会抛出如下两组类型的异常。
登录认证类异常 UnauthenticatedException.class, AuthenticationException.class
权限认证类异常 UnauthorizedException.class, AuthorizationException.class
(每个具体的异常对应哪个注解,大家查看源码了解一下)
言归正传,直接上代码,通过代码来说明本文目的 “做Get和Post请求的时候,如果请求的URL是被注解权限控制的,在没有权限或者登陆失效的情况下,如果以正确方式的返回结果(如果用户没有登录,大多数都是直接跳转到登录页面了)”。
由于项目前端框架设定,如新增一个用户,先跳转新增用户页面,然后去保存用户信息,跳转新增用户页面是get请求,保存用户信息是Post请求。
实现如下:
通过一个 BaseController 来统一处理,然后被其他 Controller 继承即可,对于JSON和页面跳转,我们只需要做一个Ajax判断处理即可。
代码如下:
import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; import java.util.Map.Entry; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.beanutils.BeanMap; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authz.AuthorizationException; import org.apache.shiro.authz.UnauthenticatedException; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.ExceptionHandler; import com.zfull.commons.result.QueryResult; import com.zfull.commons.web.vo.ReturnJsonVO; import com.zfull.commons.web.vo.ShiroAccountVO; import com.zfull.facade.authority.dto.BzMenuDTO; import com.zfull.facade.authority.query.BzMenuQuery; import com.zfull.facade.authority.service.BzMenuService; import net.sf.json.JSONArray; /** * 基础Controller * @ClassName: BaseController * @Description: TODO * @author OnlyMate * @Date 2018年4月11日 下午2:30:00 * */ public class BaseController { protected Logger log = LoggerFactory.getLogger(this.getClass()); protected final static String REDIRECT_LOGIN = "redirect:/login"; @Autowired private BzMenuService menuService; // 右侧功能菜单 public String menuRight(String urlstr) { String parMenuId = menuService.findMenuByAction(urlstr).getResults().get(0).getMenuId(); ShiroAccountVO currShiroUser = getCurrentUser(); String[] roleIds = currShiroUser.getRoleIds().split(",");// 当前登录用户所属角色 // 右侧菜单 BzMenuQuery menuQuery = new BzMenuQuery (); menuQuery.setParentId(parMenuId); menuQuery.setRoleIds(Arrays.asList(roleIds).stream().map(s -> Integer.parseInt(s.trim())).collect(Collectors.toList())); QueryResult<BzMenuDTO> source = menuService.findMenuList(menuQuery); StringBuilder htmls = new StringBuilder(); String menuids = ""; if (source != null && source.getResults().size() > 0) { for (BzMenuDTO entity : source.getResults()) { if (menuids.indexOf(entity.getMenuId()) > -1) { continue; } menuids += entity.getMenuId() + ","; if (entity.getFunction().contains("#")) { /*htmls.append( " <a href='" + entity.getMenuengname() + "'data-backdrop='static' data-toggle='modal'>"); htmls.append("<i class='" + entity.getIcon() + "'></i> "); htmls.append(entity.getMenuname() + "</a>");*/ }else { htmls.append(" <button class='btn' onclick='" + entity.getFunction() + "' >"); htmls.append("<i class='"+entity.getIcon()+"'></i>"); htmls.append("<span>"+entity.getMenuName() + "</span></button>"); } } } htmls.append(" <input type='hidden' id='chkAction' name='chkAction' value='" + urlstr + "' />"); return htmls.toString(); } public ShiroAccountVO getCurrentUser() { Subject subject = SecurityUtils.getSubject(); return (ShiroAccountVO) subject.getPrincipal(); } public String searchParams(Object obj) { BeanMap map = new BeanMap(obj); StringBuilder searchParams = new StringBuilder(); for (Entry<Object, Object> entry : map.entrySet()) { if (!"class".equals(entry.getKey()) && !"pageSize".equals(entry.getKey()) && !"flag".equals(entry.getKey()) && !"pageNum".equals(entry.getKey()) && entry.getValue() != null) { searchParams.append(entry.getKey()); searchParams.append("="); searchParams.append(entry.getValue()); searchParams.append("&"); } } return searchParams.toString(); } /*********************** 以下是重点 *************************/ /** * 登录认证异常(这个异常基本没有用,一般登录那里做了处理) * @Title: authenticationException * @Description: TODO * @Date 2018年4月11日 下午2:19:06 * @author OnlyMate * @param request * @param response * @return */ @ExceptionHandler({ UnauthenticatedException.class, AuthenticationException.class }) public String authenticationException(HttpServletRequest request, HttpServletResponse response) { if (isAjaxRequest(request)) { // 输出JSON ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); // 提交失败 1 String message = "当前登录用户无该权限"; returnJson.setMessage(message); writeJson(returnJson, response); return null; } else { return "redirect:/login"; } } /** * 权限异常 * @Title: authorizationException * @Description: TODO * @Date 2018年4月11日 下午2:19:18 * @author OnlyMate * @param request * @param response * @return */ @ExceptionHandler({ UnauthorizedException.class, AuthorizationException.class }) public String authorizationException(HttpServletRequest request, HttpServletResponse response) { if (isAjaxRequest(request)) { // 输出JSON ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); // 提交失败 1 String message = "当前登录用户无该权限"; returnJson.setMessage(message); writeJson(returnJson, response); return null; } else { return "redirect:/unauthor"; } } /** * 输出JSON * @Title: writeJson * @Description: TODO * @Date 2018年4月11日 下午2:18:10 * @author OnlyMate * @param returnJson * @param response */ private void writeJson(ReturnJsonVO returnJson, HttpServletResponse response) { PrintWriter out = null; try { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); out = response.getWriter(); out.write(JSONArray.fromObject(returnJson).toString()); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { out.close(); } } } /** * 是否是Ajax请求 * @Title: isAjaxRequest * @Description: TODO * @Date 2018年4月11日 下午2:19:31 * @author OnlyMate * @param request * @return */ public static boolean isAjaxRequest(HttpServletRequest request) { String requestedWith = request.getHeader("x-requested-with"); if (requestedWith != null && requestedWith.equalsIgnoreCase("XMLHttpRequest")) { return true; } else { return false; } } }
下面是一个普通的 Controller,继承了BaseController
import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSON; import com.google.common.collect.Maps; import com.zfull.commons.enums.basic.RoleLevelEnum; import com.zfull.commons.result.QueryResult; import com.zfull.commons.result.Result; import com.zfull.commons.result.SingleResult; import com.zfull.commons.security.CipherTools; import com.zfull.commons.utils.DateUtil; import com.zfull.commons.web.utils.JsonMapper; import com.zfull.commons.web.vo.ReturnJsonVO; import com.zfull.commons.web.vo.ShiroAccountVO; import com.zfull.facade.authority.dto.BzOperToRole; import com.zfull.facade.authority.dto.BzOperatorDTO; import com.zfull.facade.authority.dto.BzRoleDTO; import com.zfull.facade.authority.query.BzOperatorQuery; import com.zfull.facade.authority.query.BzRoleQuery; import com.zfull.facade.authority.service.BzOperatorMchtService; import com.zfull.facade.authority.service.BzRoleService; import com.zfull.facade.authority.vo.BzOperatorVO; import com.zfull.web.common.BaseController; import net.sf.json.JSONArray; @Controller @RequestMapping(value=BzOperatorMchtController.PARENT_URL) public class BzOperatorMchtController extends BaseController{ protected final static String PARENT_URL = "/permission/operatormcht"; private static JsonMapper mapper = JsonMapper.nonDefaultMapper(); @Autowired private BzOperatorMchtService operatorMchtService; @Autowired private BzRoleService roleService; /** * 用户列表 * @Title: index * @Description: TODO * @Date: 2018年3月26日 下午2:34:49 * @author: OnlyMate * @throws: * @param request * @param query * @return */ @RequiresPermissions(value="operatormcht.index") @RequestMapping(method = RequestMethod.GET) public String index(HttpServletRequest request, Model model, BzOperatorQuery query) { ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); //提交失败 1 String message = "查询用户首页出错"; // 获取当前操作员信息 ShiroAccountVO currShiroUser = getCurrentUser(); QueryResult<BzOperatorVO> result = new QueryResult<BzOperatorVO>(); try { // TODO 按照登录用户去筛选数据,查询当前的商户信息 result = operatorMchtService.queryOperatorList(query); if(result.isSuccess()) { message = "查询用户首页成功"; returnJson.setStatus("0"); returnJson.setData(JSON.toJSONString(result)); }else { message = "查询用户首页失败"; } } catch (Exception e) { message = "查询用户首页出错"; log.error(message); e.printStackTrace(); }finally { returnJson.setMessage(message); log.info("系统日志:登录名={},操作员={},ip={},日期={},操作{}的{}方法-{}", currShiroUser.getOperId(),currShiroUser.getOperName(),currShiroUser.getLoginIP(),
DateUtil.currentDatetime(),"BzOperatorMchtController","index",message); } model.addAttribute("roleinfos", roleService.findRoleList(new BzRoleQuery())); model.addAttribute("source", result); model.addAttribute("query", query); model.addAttribute("menuRight", menuRight(PARENT_URL)); model.addAttribute("searchParams", searchParams(query)); model.addAttribute("currentOperId", currShiroUser.getOperId()); return PARENT_URL + "/index"; } /** * 用户详情 * @Title: detail * @Description: TODO * @Date: 2018年3月26日 下午2:35:01 * @author: OnlyMate * @throws: * @param request * @param operId * @return */ @ResponseBody @RequiresPermissions(value="operatormcht.detail") @RequestMapping(value="/detail", method = RequestMethod.POST) public ReturnJsonVO detail(HttpServletRequest request, String operId) { ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); //提交失败 1 String message = "查询用户详情出错"; // 获取当前操作员信息 // ShiroAccountVO currShiroUser = getCurrentUser(); try { if(StringUtils.isBlank(operId)) { message = "传入参数有误"; returnJson.setMessage(message); return returnJson; } SingleResult<BzOperatorDTO> result = operatorMchtService.findByOperId(operId); if(result.isSuccess()) { returnJson.setStatus("0"); returnJson.setData(JSON.toJSONString(result.getResult())); message = "查询用户详情成功"; }else { message = "查询用户详情失败"; } } catch (Exception e) { message = "查询用户详情出错"; log.error(message); e.printStackTrace(); }finally { returnJson.setMessage(message); } return returnJson; } /** * 跳转新增用户界面 * @Title: addView * @Description: TODO * @Date: 2018年4月2日 上午1:45:45 * @author: OnlyMate * @throws: * @param model * @return */ @RequiresPermissions(value = "operatormcht.addView") @RequestMapping(value = "/addView", method = RequestMethod.GET) public String addView(Model model) { ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); // 提交失败 1 String message = "跳转新增用户页面出错"; try { //TODO 查询机构和商户信息 message = "跳转新增用户页面成功"; returnJson.setStatus("0"); returnJson.setData(JSON.toJSONString("")); } catch (Exception e) { message = "跳转新增用户页面出错"; log.error(message); e.printStackTrace(); } finally { returnJson.setMessage(message); } return PARENT_URL + "/add"; } /** * 保存用户 * @Title: add * @Description: TODO * @Date: 2018年3月26日 下午2:35:45 * @author: OnlyMate * @throws: * @param request * @param dto * @return */ @ResponseBody @RequiresPermissions(value="operatormcht.add") @RequestMapping(value="/add", method = RequestMethod.POST) public String add(HttpServletRequest request, BzOperatorDTO dto) { ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); //提交失败 1 String message = "用户新增出错"; // 获取当前操作员信息 ShiroAccountVO currShiroUser = getCurrentUser(); BzOperatorQuery query = new BzOperatorQuery(); boolean flag = Boolean.TRUE; try { if(StringUtils.isNotBlank(dto.getOperId()) && StringUtils.isNotBlank(dto.getBindPhone()) && StringUtils.isNotBlank(dto.getBindEmail())) { query.setLoginName(dto.getOperId()); if(flag && !checkLoginName(query)) { flag = Boolean.FALSE; message = "用户名已存在"; } query.setLoginName(dto.getBindPhone()); if(flag && !checkLoginName(query)){ flag = Boolean.FALSE; message = "绑定手机号已存在"; } query.setLoginName(dto.getBindEmail()); if(flag && !checkLoginName(query)) { flag = Boolean.FALSE; message = "绑定邮箱号已存在"; } if(flag) { dto.setPasswd("a94d5cd0079cfc8db030e1107de1addd1903a01b"); dto.setOnlineFlag("OFFLINE"); dto.setInitPasswd("INIT"); dto.setCreateFlag("MANUAL"); dto.setLoginCount(0); dto.setLastTime(new Date()); Result result = operatorMchtService.insertOperator(dto); if(result.isSuccess()) { message = "用户新增成功"; returnJson.setStatus("0"); }else { message = "用户新增失败"; } } }else { message = "传入参数有误"; } } catch (Exception e) { message = "用户新增出错"; log.error(message); e.printStackTrace(); }finally { returnJson.setMessage(message); log.info("系统日志:登录名={},操作员={},ip={},日期={},操作{}的{}方法-{}", currShiroUser.getOperId(),currShiroUser.getOperName(),currShiroUser.getLoginIP(),
DateUtil.currentDatetime(),"BzOperatorMchtController","add",message); } return JSONArray.fromObject(returnJson).toString(); } /** * 跳转用户编辑页面 * @Title: editView * @Description: TODO * @Date: 2018年4月2日 上午10:44:10 * @author: OnlyMate * @throws: * @param model * @param query * @return */ @RequiresPermissions(value = "operatormcht.editView") @RequestMapping(value = "/editView", method = RequestMethod.GET) public String editView(Model model, BzOperatorQuery query) { ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); // 提交失败 1 String message = "跳转编辑用户页面出错"; BzOperatorDTO oper = new BzOperatorDTO(); try { if (StringUtils.isBlank(query.getOperId())) { message = "传入参数有误"; }else { oper = operatorMchtService.findByOperId(query.getOperId()).getResult(); message = "跳转编辑用户页面成功"; returnJson.setStatus("0"); returnJson.setData(JSON.toJSONString(oper)); } } catch (Exception e) { message = "跳转编辑用户页面出错"; log.error(message); e.printStackTrace(); } finally { returnJson.setMessage(message); } model.addAttribute("oper", oper); return PARENT_URL + "/edit"; } /** * 更新用户 * @Title: edit * @Description: TODO * @Date: 2018年3月26日 下午2:36:02 * @author: OnlyMate * @throws: * @param request * @param dto * @return */ @ResponseBody @RequiresPermissions(value="operatormcht.edit") @RequestMapping(value="/edit", method = RequestMethod.POST) public String edit(HttpServletRequest request, BzOperatorDTO dto) { ReturnJsonVO returnJson = new ReturnJsonVO(); returnJson.setStatus(ReturnJsonVO.SUBMIT_FAILURE); //提交失败 1 String message = "用户更新出错"; // 获取当前操作员信息 ShiroAccountVO currShiroUser = getCurrentUser(); BzOperatorQuery query = new BzOperatorQuery(); boolean flag = Boolean.TRUE; try { if(StringUtils.isNotBlank(dto.getOperId()) && StringUtils.isNotBlank(dto.getBindPhone()) && StringUtils.isNotBlank(dto.getBindEmail())) { query.setOperId(dto.getOperId()); query.setLoginName(dto.getOperId()); if(flag && !checkLoginName(query)) { flag = Boolean.FALSE; message = "用户名已存在"; } query.setLoginName(dto.getBindPhone()); if(flag && !checkLoginName(query)){ flag = Boolean.FALSE; message = "绑定手机号已存在"; } query.setLoginName(dto.getBindEmail()); if(flag && !checkLoginName(query)) { flag = Boolean.FALSE; message = "绑定邮箱号已存在"; } if(flag) { BzOperatorDTO oldOperator = operatorMchtService.findByOperId(dto.getOperId()).getResult(); dto.setOnlineFlag(oldOperator.getOnlineFlag()); dto.setInitPasswd(oldOperator.getInitPasswd()); dto.setCreateFlag(oldOperator.getCreateFlag()); dto.setLoginCount(oldOperator.getLoginCount()); dto.setLastTime(new Date()); Result result = operatorMchtService.updateOperator(dto); if(result.isSuccess()) { message = "用户更新成功"; returnJson.setStatus("0"); }else { message = "用户更新失败"; } } }else { message = "传入参数有误"; } } catch (Exception e) { message = "用户更新出错"; log.error(message); e.printStackTrace(); }finally { returnJson.setMessage(message); log.info("系统日志:登录名={},操作员={},ip={},日期={},操作{}的{}方法-{}", currShiroUser.getOperId(),currShiroUser.getOperName(),currShiroUser.getLoginIP(),
DateUtil.currentDatetime(),"BzOperatorMchtController","edit",message); } return JSONArray.fromObject(returnJson).toString(); } }
未授权路径
/** * 未授权页面 * @Title: unauthor * @Description: TODO * @Date 2018年4月11日 上午12:19:37 * @author OnlyMate * @param request * @param response * @param model * @return */ @RequestMapping(value = "/unauthor", method = RequestMethod.GET) public String unauthor(HttpServletRequest request, HttpServletResponse response, Model model){ return "/unauthor"; }
未授权页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <c:set var="ctx" value="${pageContext.request.contextPath}" /> <!DOCTYPE HTML> <head> <link rel="stylesheet" href="${ctx}/static/lib/jquery-ztree/css/zTreeStyle.css" type="text/css" /> <link rel="stylesheet" href="${ctx}/static/lib/bootstrap/css/bootstrap.css"> <link rel="stylesheet" href="${ctx}/static/css/reset.css"> <link rel="stylesheet" href="${ctx}/static/lib/jquery.mCustomScrollbar/jquery.mCustomScrollbar.css"> <link rel="stylesheet" href="${ctx}/static/css/index.css"> <link rel="stylesheet" href="${ctx}/static/css/main.css"> <link rel="stylesheet" href="${ctx}/static/css/style.css"> <link rel="stylesheet" href="${ctx}/static/img/splashy/splashy.css"> <link href="${ctx}/static/lib/font-awesome/css/font-awesome.min.css" rel="stylesheet" type="text/css"/> </head> <body> <div class="main_con"> <div class="btn-actions"> <span style="color: red;font-size: 20px;">当前登录用户无该权限</span> </div> </div> </body> </html>
当我们使用 get方式去请求/permission/operatormcht/addView时,如果用户没有授权,则重定向"redirect:/unauthor"到unauthor.jsp页面。
效果如下:
当我们使用 post方式去请求/permission/operatormcht/edit时,如果用户没有授权,则会返回没有授权的JSON结果,而不是页面。
效果如下:
这样解决了,在shrio权限校验中,区分Get和Post请求以正确的方式去返回对应的信息,get返回没有授权的页面,post返回没有授权的Json信息。