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查询角色权限表,判断当前用户是否可以操作该方法。