filter过滤器实现权限访问控制以及同一账号只能登录一台设备

需求:如题目所意,未登录用户不能浏览访问项目内部的资源,对访问的请求和响应进行拦截,且一个用户只能在一台设备登录。

权限访问控制功能可以通过过滤器或者拦截器去实现,在这里我用的是过滤器,过滤器可以过滤全部action请求,拦截器则更有针对性,对某一请求进行特定处理。

filter过滤器知识梳理:

Filter也称之为过滤器,它是Servlet三大器之一(另外两个为监听器和拦截器)。通过Filter技术,可以对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇(聊天室经常用到的,一些骂人的话)、压缩响应信息等一些高级功能。
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,如图所示:

j

Filter拦截的实现
  Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
调用目标资源之前,让一段代码执行。
是否调用目标资源(即是否让用户访问web资源)。
调用目标资源之后,让一段代码执行。
  web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对 象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方 法,即web资源就会被访问,否则web资源不会被访问。
Filter开发分为二个步骤:
编写java类实现Filter接口,并实现其doFilter方法。
在 web.xml 文件中使用和配对元素对编写的filter类进行注册,并设置它所能拦截的资源。


项目案例:

1、数据库
对于确保一个账号只在一台设备登录,先登录用户会被后登录用户挤掉,所以在用户表设计了一个sessionId字段,用来保存浏览器分配的sessionId。即用同一浏览器浏览器登录访问网站时,假如在不关闭浏览器的情况下,访问登录同一网站,生成的sessionId是一样的。

2、登录权限过滤器类


/**
 * 用户权限校验过滤器,针对URL进行权限校验
 */
public class AuthorityFilter implements Filter {// 所有自定义的过滤器都需实现filter接口

    // 不需要校验权限的URL  容器启动 过滤器初始化时会先加载web.xml文件中配置的不需要校验权限的URL
    String[] noCheckPaths = null;
    // 访问的工程名  同上,假如是访问localhost:8080/user   得到的是/user
    String contextPath = null;

    // 用redis缓存sessionId  每次请求时直接缓存数据库中查找,优化速度
    private RedisTemplate redisTemplate;

    // 销毁
    public void destroy() {
    }

    // 过滤执行的逻辑代码
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String path = request.getServletPath();
        // 获取当前浏览器访问网站生成的sessionId
        String currentSessionId = request.getSession().getId();

        ServletContext sc = request.getSession().getServletContext();
        XmlWebApplicationContext cxt = (XmlWebApplicationContext) WebApplicationContextUtils.getWebApplicationContext(sc);
        // 从容器中获取redisTemplate  bean
        // if(cxt != null && cxt.getBean("redisTemplate") != null)
        redisTemplate = (RedisTemplate) cxt.getBean("redisTemplate");

        // 从session取得登录用户信息
        User user = (User) request.getSession().getAttribute("User");

        if(user != null ){// 用户信息不为空
            // 从redis缓存数据库取出该用户的sessionId
            String dbSessionId = (String) redisTemplate.opsForValue().get(user.getUserId());
            if(currentSessionId.endsWith(dbSessionId)){// 作比较,相等则放行
                chain.doFilter(servletRequest, servletResponse);
            }else{
                if (this.isVerification(path)) {// sessionId不相等则判断请求url是否要验证  为true则跳转到登录页面
                    String toPath = "/login";
                    response.setContentType("text/html; charset=utf-8"); // 防止出现乱码
                    response.getWriter().write("<script>alert('"+"该账号已在别的设备登录,如非本人操作,请及时修改密码,确保资金安全!"+"')</script>");
                    // 因为项目中用到iframe,故需要在顶层窗口跳转
                    response.getWriter().write("<script>top.location.href='" + contextPath + toPath + "'</script>");
                    return;
                } else {
                    chain.doFilter(servletRequest, servletResponse);
                }
            }

        } else {// 用户信息为空
            // 判断url是否验证
            if (this.isVerification(path)) {//  为true则跳转到登录页面
                String toPath = "/login";
                // 顶层窗口即浏览器窗口框打开  应对frame嵌套
                response.getWriter().write("<script>top.location.href='" + contextPath + toPath + "'</script>");
                // response.getWriter().write("<script>window.parent.location.href='" + contextPath + toPath + "'</script>");// 在父窗口打开
                // response.sendRedirect(contextPath + toPath);  // 重定向
                //response.getWriter().write("<script>window.location.href='" + contextPath + toPath + "'</script>"); // 在当前窗口打开
                return;
            } else {// 放行
                chain.doFilter(servletRequest, servletResponse);
            }
        }
    }


    /**
     * 
     * isVerification(判断指定的url是否需要验证,默认是需要验证的) (这里描述这个方法适用条件 – 可选)
     * @param path
     * @return false代表不用验证 true 代表需要进行验证 Boolean 
     * @exception
     */
    private Boolean isVerification(String path) {
        // 默认需要验证url
        boolean isVerification = true;

        if ("/".equals(path)) { // 防止无限重定向
            return false;
        }
        for (String p : noCheckPaths) {// 判断该请求url是否需要验证
            if (path.indexOf(p) == 0) {
                isVerification = false;
                break;
            }
        }
        // "/view/" 路径下的js css,不校验
        if (path.indexOf("/view/") == 0
                && (path.indexOf(".js") > 0 || path.indexOf(".js?") > 0 || path.indexOf(".css") > 0)) {
            isVerification = false;
        }
        return isVerification;
    }


    /**
     *  服务器启动是过滤器初始化的参数  加载不需要验证的url
     */
    public void init(FilterConfig config) throws ServletException {
        // 获取web.xml文件的unCheckPaths元素的内容,即配置的不需要验证的url
        String unCheckPath = config.getInitParameter("unCheckPaths");
        this.noCheckPaths = unCheckPath.split(",");
        // 获取工程名
        contextPath = config.getServletContext().getContextPath();
    }
}

3、登录controller

@Controller
@RequestMapping("/login")
public class LoginController extends BaseController {

    @Resource
    private UserService userService;

    /**
     * 用户登录
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public String login(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        try {
            String userId = request.getparameter("userId");
            String userPwd = request.getparameter("userPwd");
            // 根据用户名到数据库查找此用户
            User user = this.userService.queryUser(userId);
            if (user == null) {
                return JsonUtils.getJsonCom("1", "该用户不存在", "");
            } else {
                if (!userPwd.equals(user.getUserPwd())) {
                    return JsonUtils.getJsonCom("2", "帐号或者密码错误", "");
                }
                if (user.getValidStatus() == 1) {
                    return JsonUtils.getJsonCom("3", "该帐号已被停用", "");
                }
            }

            HttpSession session = request.getSession(true);
            // 获取新会话的sessionId
            String sessionId = session.getId();
            // 与数据库的sessionId作比较
            if (!sessionId.equals(user.getSessionId())) {
                // 不相同则同步缓存到redis       
                redisTemplate.opsForValue().set(user.getUserId(),sessionId);
            }
            // 缓存用户信息到session
            session.setAttribute("User", user);

            return JsonUtils.getJsonCom("0", "成功", "");
        } catch (Exception e) {
            // log.info("登陆数据出现异常...");// 日志输出
            // e.printStackTrace();
            return JsonUtils.getJsonCom("4", "登录请求出错,请重试!", "");
        }
    }

4、web.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    <context-param>
        <!-- 加载spring配置文件 -->
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/applicationContext.xml
        </param-value>
    </context-param>

    <!-- spring mvc DispatcherServlet -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/spring-mvc.xml</param-value>
        </init-param>
        <!-- 表示容器在启动时就加载并初始化这个servlet -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- URL权限过滤 -->
    <filter>
        <filter-name>authorityFilter</filter-name>
        <!-- 注册过滤器类 -->
        <filter-class>com.qly.b2b.web.common.AuthorityFilter</filter-class>
        <init-param>
            <!-- 设置编码 -->
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <!-- 设置不需要验证的url路径包括js css 图片等信息  根据自己需要增减 -->
            <param-name>unCheckPaths</param-name>
            <param-value>/images,/queryinfo,/js,/css,/login,/desklogin,/index/images</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>authorityFilter</filter-name>
        <!-- 对所有路径过滤 -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>/view/login/index.jsp</welcome-file>
    </welcome-file-list>
</web-app>

5、登录页面jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE>
<html>
<head>
<meta name="viewport" content="user-scalable=yes, width=1225, target-densityDpi=device-dpi" />
<script src="<%=basePath%>js/jquery.js"></script>
<link rel="stylesheet" type="text/css" href="<%=basePath%>css/iconfont/iconfont.css">
<script src="<%=basePath%>js/jquery-1.11.3.min.js"></script>

<link rel="stylesheet" href="<%=basePath%>css/jquery-ui.css">
<link rel="stylesheet" href="<%=basePath%>css/stylesheet.css">
<link rel="stylesheet" href="<%=basePath%>css/initial.css">
<link rel="stylesheet" href="<%=basePath%>css/style.css">
<script type="text/javascript" src="<%=basePath%>js/jquery-ui.js"></script>
<script type="text/javascript" src="<%=basePath%>js/dialog.js"></script>
<script type="text/javascript" src="<%=basePath%>js/libs.js"></script>


</head>
<body id="bodyid" huaban_collector_injected="true" onkeydown="loginByEnter(event)">

<div class="main">

    <div class="new_login_cover"></div>
    <div class="new_login">
        <div class="new_user new_input">
            <input type="text" id="userId" placeholder="输入会员账号" tabindex="1" onfocus="this.placeholder=''" onblur="this.placeholder='请输入会员账号'">
        </div>
        <div class="new_password new_input">
            <input type="password" id="password" placeholder="输入密码" tabindex="2" onfocus="this.placeholder=''" onblur="this.placeholder='输入密码'">
        </div>
        <div class="new_logins new_input ">
            <button type="button" id="btnlogin" class="btn" tabindex="4">登录</button>
        </div> 
    </div>
</div>

<script type="text/javascript">

$(function(){
    $("#btnlogin").click(function(){
        var userId=$("#userId").val();
        var userPwd=$("#password").val();

        if(userId == "" || userId == null){
            alert("请填写登录帐号");
            return;
        }
        if(userPwd == "" || userPwd == null){
            alert("请填写登录密码");
            return;
        }

        var param = "userId=" + userId + "&userPwd=" + userPwd;
        $.ajax({ 
             url: '<%=basePath%>login/login',
             data:param,
             dataType:"json",
             type: 'POST',
             async: false, 
             success: function(data){
                 if(data.code == 0){
                    alert("登录成功");;
                 }else{
                     alert(data.reason);
                 }   
             }
        });
    });
})
</script>
</body>
</html>

6、redis、mysql数据库的b2bconfig.properties属性文件

# mysql settings
db.jdbc.driverClass=com.mysql.jdbc.Driver
db.jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
db.jdbc.username=root
db.jdbc.password=123
db.initialPoolSize=5
db.minPoolSize=5
db.maxPoolSize=30
db.maxStatements=100
db.maxIdleTime=3600
db.acquireIncrement=2
db.acquireRetryAttempts=10
db.acquireRetryDelay=600
db.checkoutTimeout=100000


# Redis settings
redis.host=127.0.0.1
redis.port=6379
# 若没有设置可注释忽略
redis.password=PingPing
redis.maxIdle=300
redis.maxTotal=600
redis.maxWaitMillis=1000
redis.testOnBorrow=true

7、spring配置文件

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd 
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 
    http://www.springframework.org/schema/aop  
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- 父容器中移除Controller注解,在子容器中扫描注入 -->
    <context:component-scan base-package="com.qly.b2b">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
    </context:component-scan>

    <context:property-placeholder ignore-unresolvable="true" location="classpath:b2bconfig.properties" />

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        > <!--  destroy-method="close"> -->
        <property name="driverClass">
            <value>${db.jdbc.driverClass}</value>
        </property>
        <property name="jdbcUrl">
            <value>${db.jdbc.url}</value>
        </property>
        <property name="user">
            <value>${db.jdbc.username}</value>
        </property>
        <property name="password">
            <value>${db.jdbc.password}</value>
        </property>
        <property name="initialPoolSize">
            <value>${db.initialPoolSize}</value>
        </property>
        <property name="minPoolSize">
            <value>${db.minPoolSize}</value>
        </property>
        <property name="maxPoolSize">
            <value>${db.maxPoolSize}</value>
        </property>
        <property name="maxStatements">
            <value>${db.maxStatements}</value>
        </property>
        <property name="maxIdleTime">
            <value>${db.maxIdleTime}</value>
        </property>
        <property name="acquireIncrement">
            <value>${db.acquireIncrement}</value>
        </property>
        <property name="acquireRetryAttempts">
            <value>${db.acquireRetryAttempts}</value>
        </property>
        <property name="acquireRetryDelay">
            <value>${db.acquireRetryDelay}</value>
        </property>
        <property name="checkoutTimeout">
            <value>${db.checkoutTimeout}</value>
        </property>
    </bean>


    <!--  dao层用到mybatis框架  mybatis文件配置,扫描所有mapper文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- <property name="typeAliasesPackage" value="com.szkingdom.warren.bean,com.szkingdom.iagroup.bean" 
            /> -->
        <!-- <property name="configLocation" value="classpath:b2bconfig.xml"/> -->
        <property name="mapperLocations" value="classpath*:com/test/b2b/dao/*/*.xml" />
    </bean>

    <!-- 配置sqlSessionTemplate -->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>


    <!-- 映射Mapper -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.qly.b2b.dao,com.qly.b2b.dao.config" />
    </bean>

    <!--事务管理数据库连接池 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--数据库事务配置 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>


    <!-- 1.创建jedis连接池配置对象 -->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--最大空闲数-->  
        <property name="maxIdle" value="${redis.maxIdle}" />  
        <!-- 最大连接数 -->
        <property name="maxTotal" value="${redis.maxTotal}" />
        <!-- 最大建立连接等待时间 -->
        <property name="maxWaitMillis" value="${redis.maxWaitMillis}" /> 
         <!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->  
        <property name="testOnBorrow" value="${redis.testOnBorrow}" />
    </bean>
    <!-- 2.创建连接工厂对象 (类似:LocalSessionFactoryBean) -->
    <bean id="connectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- 关联 连接池配置对象 -->
        <property name="poolConfig" ref="poolConfig" />
        <!-- redis的主机 -->
        <property name="hostName" value="${redis.host}" />
        <!-- redis的端口 -->
        <property name="port" value="${redis.port}" />
        <property name="password" value="${redis.password}" />
        <!-- 指定操作数据库索引,从0开始 -->
        <property name="database" value="1" />
    </bean>
    <!-- 3.创建RedisTemlate(类似:HibernateTemplate) -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <!-- 关联连接工厂 -->
        <property name="connectionFactory" ref="connectionFactory" />
        <!--spring data redis提供序列化器 -->
        <!--key的序列化器 -->
        <property name="keySerializer">
            <bean
                class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property><!-- value的序列化器 -->
        <property name="valueSerializer">
            <bean
                class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
    </bean>

</beans>

8、springmvc配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:annotation-config />
    <!-- 把标记了@Controller注解的类转换为bean -->
    <context:component-scan base-package="com.test.b2b.controller" />

    <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀 -->
    <mvc:annotation-driven />
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
        <property name="prefix" value="/view/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <mvc:view-controller path="/" view-name="index/index" />


    <!-- 处理返回中文乱码情况 -->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean
                    class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/html;charset=UTF-8</value>
                            <value>text/plain;charset=UTF-8</value>
                            <value>application/json;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
</beans>

9、service层 dao层代码省略。。。。


若有更好的解决方案,欢迎讨论!谢谢

猜你喜欢

转载自blog.csdn.net/u013068184/article/details/81137120