1 粗顆粒度權限控制(使用過濾器完成)
分析:
精確到Session的權限控制(判斷Session是否存在)
使用過濾器完成粗顆粒的權限控制,如果Session不存在就跳轉到首頁,如果存在可以通過URL鏈接訪問到對應的操作。
第一步:定義一個過濾器:
public class SystemFilter implements Filter {
/**web容器啟動的時候,執行的方法*/
//存放沒有Session之前,需要放行的連接
List<String> list = new ArrayList<String>();
public void init(FilterConfig config) throws ServletException {
list.add("/index.jsp");
list.add("/image.jsp");
list.add("/system/elecMenuAction_menuHome.do");
}
/**每次訪問URL連接的時候,先執行過濾器的doFilter的方法*/
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//獲取訪問的連接地址
String path = request.getServletPath();
//在訪問首頁index.jsp頁面之前,先從Cookie中獲取name,password的值,並顯示在頁面上(完成記住我)
this.forwordIndexPage(path,request);
//如果訪問的路徑path包含在放行的List的存放的連接的時候,此時需要放行
if(list.contains(path)){
chain.doFilter(request, response);
return;
}
//獲取用戶登錄的Session
ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
//放行
if(elecUser!=null){
chain.doFilter(request, response);
return;
}
//重定向到登錄頁面
response.sendRedirect(request.getContextPath()+"/index.jsp");
}
/**銷毀*/
public void destroy() {
}
/**在訪問首頁index.jsp頁面之前,先從Cookie中獲取name,password的值,並顯示在頁面上(完成記住我)*/
private void forwordIndexPage(String path, HttpServletRequest request) {
if(path!=null && path.equals("/index.jsp")){
String name = "";
String password = "";
String checked = "";
Cookie [] cookies = request.getCookies();
if(cookies!=null && cookies.length>0){
for(Cookie cookie:cookies){
if(cookie.getName().equals("name")){
name = cookie.getValue();
/**
* 如果name出現中文,對中文進行解碼
*/
try {
name = URLDecoder.decode(name, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
checked = "checked";
}
if(cookie.getName().equals("password")){
password = cookie.getValue();
}
}
}
request.setAttribute("name", name);
request.setAttribute("password", password);
request.setAttribute("checked", checked);
}
}
}
第二步:在web容器中添加對應的過濾器:
<!-- 自定義過濾器,要求添加到struts2過濾器的前面 -->
<filter>
<filter-name>SystemFilter</filter-name>
<filter-class>cn.itcast.elec.util.SystemFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SystemFilter</filter-name>
<url-pattern>*.do</url-pattern>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
問題:不夠友好,訪問鏈接,最好提示【非法操作,系統將會5秒后跳轉到登錄頁面】
修改過濾器類中的操作:
(1)在過濾器中的init方法中添加2個放行的連接:
list.add("/error.jsp");
list.add("/system/elecMenuAction_logout.do");
(2)在doFilter的方法重定向到error.jsp
將:
//重定向到登錄頁面
response.sendRedirect(request.getContextPath()+"/index.jsp");
修改成:
//重定向到error.jsp(5秒跳轉到登錄名頁面)
response.sendRedirect(request.getContextPath()+"/error.jsp");
(3)error.jsp的內容:
<script>
var i=6;
var t;
function showTimer(){
if(i==0){//如果秒數為0的話,清除t,防止一直調用函數,對於反應慢的機器可能實現不了跳轉到的效果,所以要清除掉 setInterval()
parent.location.href="${pageContext.request.contextPath }/system/elecMenuAction_logout.do";
window.clearInterval(t);
}else{
i = i - 1 ;
// 秒數減少並插入 timer 層中
document.getElementById("timer").innerHTML= i+"秒";
}
}
// 每隔一秒鍾調用一次函數 showTimer()
t = window.setInterval(showTimer,1000);
</script>
注意:Session不應該在服務器一直不清空,如果Session過多,會導致Session壓力大,系統變慢,於是要求10分鍾如果不操作系統,將Session自動清空。在web.xml中配置
<session-config>
<session-timeout>10</session-timeout>
</session-config>
粗顆粒的權限控制的面試:
使用過濾器
在過濾器中定義放行的連接,因為不是每個操作都會存在Session
在過濾器中獲取登錄后存放的Session,如果Session不為空,則放行,即可以操作定義的業務功能,如果Session為空,則跳轉到登錄頁面。
控制訪問的系統必須要存在Session
2 細顆粒權限控制(使用struts2的攔截器)
/**
* 自定義注解
*/
//被這個注解修飾的注解,利用反射,將其他的注解讀取出來
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationLimit {
String mid();//權限的code
String pid();//父級權限的code
}
public class ErrorAndLimitInterceptor extends MethodFilterInterceptor {
/**攔截器*/
@Override
protected String doIntercept(ActionInvocation actioninvocation) throws Exception {
//把自定義錯誤信息 放置到request中
HttpServletRequest request = (HttpServletRequest) actioninvocation
.getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
try {
//獲取請求的action對象
Object action = actioninvocation.getAction();
//獲取請求的方法的名稱
String methodName = actioninvocation.getProxy().getMethod();
//獲取action中的方法的封裝類(action中的方法沒有參數)
Method method = action.getClass().getMethod(methodName, null);
// Action的返回值
String result = null;
//在完成跳轉Action之前完成細顆粒權限控制,控制Action的每個方法
//檢查注解,是否可以操作權限的URL
//boolean flag = isCheckLimit(request,method);
if(true){
// 運行被攔截的Action,期間如果發生異常會被catch住
result = actioninvocation.invoke();
}
else{
request.setAttribute("errorMsg", "對不起!您沒有權限操作此功能!");
return "errorMsg";
}
return result;
} catch (Exception e) {
/**
* 處理異常
*/
String errorMsg = "出現錯誤信息,請查看日志!";
//通過instanceof判斷到底是什么異常類型
if (e instanceof RuntimeException) {
//未知的運行時異常
RuntimeException re = (RuntimeException) e;
//re.printStackTrace();
errorMsg = re.getMessage().trim();
}
/**
* 發送錯誤消息到頁面
*/
request.setAttribute("errorMsg", errorMsg);
/**
* log4j記錄日志
*/
Log log = LogFactory
.getLog(actioninvocation.getAction().getClass());
log.error(errorMsg, e);
return "errorMsg";
}// ...end of catch
}
/**驗證細顆粒權限控制*/
public boolean isCheckLimit(HttpServletRequest request, Method method) {
if(method == null){
return false;
}
//獲取當前的登陸用戶
ElecUser elecUser = (ElecUser)request.getSession().getAttribute("globle_user");
if(elecUser == null){
return false;
}
//獲取當前登陸用戶的角色(一個用戶可以對應多個角色)
Hashtable<String, String> ht = (Hashtable)request.getSession().getAttribute("globle_role");
if(ht == null){
return false;
}
//處理注解,判斷方法上是否存在注解(注解的名稱為:AnnotationLimit)
/*
* 例如:
* @AnnotationLimit(mid="aa",pid="0")
public String home(){
*/
boolean isAnnotationPresent = method.isAnnotationPresent(AnnotationLimit.class);
//不存在注解(此時不能操作該方法)
if(!isAnnotationPresent){
return false;
}
//存在注解(調用注解)
AnnotationLimit limit = method.getAnnotation(AnnotationLimit.class);
//獲取注解上的值
String mid = limit.mid(); //權限子模塊名稱
String pid = limit.pid(); //權限父操作名稱
/**
* 如果登陸用戶的角色id+注解上的@AnnotationLimit(mid="aa",pid="0")
* * 在elec_role_popedom表中存在 flag=true,此時可以訪問Action的方法;
* * 在elec_role_popedom表中不存在 flag=false,此時不能訪問Action的方法;
*/
boolean flag = false;
//攔截器中加載spring容器,從而獲取Service類,使用Service類查詢對應的用戶信息
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
IElecRoleService elecRoleService = (IElecRoleService)wac.getBean(IElecRoleService.SERVICE_NAME);
//遍歷角色ID
if(ht!=null && ht.size()>0){
for(Iterator<Entry<String, String>> ite = ht.entrySet().iterator();ite.hasNext();){
Entry<String, String> entry = ite.next();
//獲取角色ID
String roleID = entry.getKey();
flag = elecRoleService.findRolePopedomByID(roleID, mid, pid);
if(flag){
break;
}
}
}
return flag;
}
問題:為什么在struts2的攔截器中都要使用roleID,mid,pid去查詢一遍數據庫,而為什么不從Session中獲取mid的值和注解上定義的mid的值進行比較呢?
回答:此時不安全,如果盜用賬戶,登錄系統(一定有Session),即可以操作每一個執行的方法。但是由於掛失后,數據庫存放的數據發生變化,操作每一個功能之前都會先查詢權限,這樣查詢才能保證數據安全。
細顆粒的權限控制的面試:
使用struts2的攔截器
定義一個注解(mid和pid),對應權限code和父級權限的code,將注解添加到Action類中方法的上面
每個Action類的方法上添加注解(mid=””,pid=””),表示方法的惟一標識(即該方法所具有的權限)
在struts2的攔截器中,從Session中獲取角色ID,獲取Action類方法上的注解(mid和pid),使用角色ID,mid和pid查詢角色權限表,判斷當前用戶是否可以操作該方法。