引入: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信息。