Spring MVC拦截器实现用户登录权限验证
通过 Spring MVC 拦截器(Interceptor)来实现一个用户登录权限验证的案例。只有登录后的用户才能访问系统主页,如果没有登录就直接访问主页,则拦截器会将请求拦截并跳转到登录页面,同时在登录页面中给出提示信息。若用户登陆时,用户名或密码错误,则登录页也会显示相应的提示信息。已登录的用户在系统主页点击“退出登录”时,跳转会登录页面,流程图如下。
一、 新建一个名为“ssm02”的 Web 工程模块,并将与 Spring MVC 相关的依赖包引入到工程中。
导入依赖
<dependencies> <!-- spring核心容器包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.18</version> </dependency> <!-- spring切面 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.18</version> </dependency> <!--aop联盟包--> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <!-- 德鲁伊连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <!--MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- spring jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.20</version> </dependency> <!-- MySQL事务包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.18</version> </dependency> <!-- spring-orm映射依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.18</version> </dependency> <!-- Apache Commons日志包 --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <!-- log4j2 日志 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.17.1</version> <scope>test</scope> </dependency> <!-- lombok包 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency> <!-- spring test测试支持包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.18</version> <scope>test</scope> </dependency> <!-- junit5单元测试 --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <!-- springMVC支持包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.3.20</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <!-- JSON支持 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency> <!-- mybatis核心 jar包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!-- mybatis-spring整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> <!--jsp 和Servlet 可选--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 --> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring5</artifactId> <version>3.1.0.M2</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>6</source> <target>6</target> </configuration> </plugin> </plugins> </build>
二、更改web.xml 中文件内容,配置 Spring MVC 的 DispatcherServlet、请求和响应编码过滤器等信息,配置内容如下。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!--spring核心配置文件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- ContextLoaderListener监听器 (1)ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。 (2)在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--请求和响应的字符串过滤器--> <!--请求和响应的字符串过滤器--> <!--请求和响应的字符串过滤器--> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <!--设置请求的编码--> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <!--设置响应的编码,这里我们可以省略--> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置springMvc前端控制器,对浏览器发送的请求统一处理--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--配置DispatcherServlet的一个初始化参数:spring mvc配置文件的位置和名称--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <!--设置springmvc的核心控制器所能处理的请求路径 /所匹配的请求可以是 /login 或者 .html 或者.js或者css请求路径 但是/不能匹配.jsp请求的路径的请求 --> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
三、补充项目结构,准备好MVC模式下的主要目录
注意:要通过 Mark Directory as 将补充的目录进行设置,选择的参数说明:
- Sources Root:告诉idea这个文件夹及其子文件夹中包含源代码,是需要编译构建的一部分
- Test Sources Root:测试源文件夹允许您将与测试相关的代码与生产代码分开。通常,源和测试源的编译结果被放置在不同的文件夹中。
- Resources Root:用于应用程序中的资源文件(图像、各种配置XML和属性文件等)。
- 在构建过程中,资源文件夹的所有内容都复制到输出文件夹中,如下所示。
- 类似于源,您可以指定生成资源。您还可以指定输出文件夹中的文件夹,您的资源应该复制到
- Test Resources Root:测试的资源文件
- Exclued:不包括、排除
四、在resource目录下准备配置文件
4.1.创建 log4j2.xml 内容如下
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="DEBUG"> <Appenders> <Console name="Console" target="SYSTEM_ERR"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n" /> </Console> </Appenders> <Loggers> <Root level="DEBUG"> <AppenderRef ref="Console" /> </Root> </Loggers> </Configuration>
4.2.创建 jdbc.properties 内容如下
jdbc_driver=com.mysql.cj.jdbc.Driver jdbc_url=jdbc:mysql://127.0.0.1:3306/mydb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai jdbc_username=test jdbc_password=123456
4.3.创建 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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd "> <!--加载外部属性文件 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--扫描service层--> <context:component-scan base-package="com.augus.service"></context:component-scan> <!--配置德鲁伊连接池--> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc_username}"></property> <property name="password" value="${jdbc_password}"></property> <property name="url" value="${jdbc_url}"></property> <property name="driverClassName" value="${jdbc_driver}"></property> </bean> <!-- SqlSessionFactoryBean能在Spring IoC容器中以SqlSessionFactory的类型保存并被获取。就是继承了FactoryBean这个接口了,这是个支持泛型的接口: 当实现了这个接口的Bean在配置为被Spring接管时,存入IoC容器中的实例类型将会是实例化泛型的那个类型,从IoC容器中获取时也是实例化泛型的那个类型, 这种情况下,Spring 将会在应用启动时为你创建SqlSessionFactory对象,然后将它以 SqlSessionFactory为名来存储。当把这个bean注入到Spring中去了以后,IoC容器中的其他类型就可以拿到SqlSession实例了,就可以进行相关的SQL执行任务了。 --> <bean id="sessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--获取上面的数据源--> <property name="dataSource" ref="druidDataSource"></property> <property name="typeAliasesPackage" value="com.augus.pojo"></property> </bean> <!-- 配置MapperScanner 扫描mapper.xml接口 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--配置SQLSessionFactory--> <property name="sqlSessionFactoryBeanName" value="sessionFactoryBean"></property> <!--配置mapper扫描--> <property name="basePackage" value="com.augus.mapper"></property> </bean> <!--配置事务管理器--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"></property> </bean> <!--开启事务注解--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven> </beans>
4.4.创建 springmvc.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:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--开启组件扫描--> <context:component-scan base-package="com.augus.controller"/> <mvc:annotation-driven/> <!-- 配置 Thymeleaf 视图解析器 --> <!--配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--配置前置解析器--> <property name="prefix" value="/WEB-INF/templates/"/> <!--配置后置解析器--> <property name="suffix" value=".jsp"/> </bean> <!--配置静态资源放行--> <!--<mvc:resources mapping="/js/**" location="/WEB-INF/js/"/>--> <mvc:resources mapping="/upload/**" location="/upload/"/> <!--文件上传组件 id属性的值必须为multipartResolver --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置上传文件的默认编码格式--> <property name="defaultEncoding" value="UTF-8"/> <!--在这里控制上传文件的大小 不推荐 这时候没办法给前端返回提示信息,推荐在controller中配置--> <!--<property name="maxUploadSize" value="10240000000"></property>--> </bean> <!--配置拦截器--> <mvc:interceptors> <!--拦截器1--> <mvc:interceptor> <!--配置拦截器拦截的请求路径--> <mvc:mapping path="/**"/> <!--定义在这里,表示拦截器只对指定路径的请求进行拦截--> <bean id="interceptor" class="com.augus.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors> </beans>
五、在java下创建包com.augusu,在分别创建四个包
5.1.新建pojo包,在下面创建User实体类
- 在数据库中创建user表
- SQL如下:
CREATE TABLE `user` ( `uid` int(255) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`uid`) )
- 实体类:User
package com.augus.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @AllArgsConstructor @NoArgsConstructor @Data public class User implements Serializable { private Integer uid; private String username; private String password; }
5.2.新建mapper包
在下面创建UserMapper接口,代码如下:
package com.augus.mapper; import com.augus.pojo.User; import org.apache.ibatis.annotations.Param; public interface UserMapper { User findByUserName(@Param("name") String username); }
在下面创建UserMapper.xml,从表中查询数据
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.augus.mapper.UserMapper"> <!--User findUser(String uname, String password);--> <select id="findByUserName" resultType="user"> select * from user where username = #{name} </select> </mapper>
5.3.新建service包
创建接口UserService
package com.augus.service; import com.augus.pojo.User; public interface UserService { User findByUserName(String uname); }
创建impl包,创建UserServiceImpl实现类
package com.augus.service.impl; import com.augus.mapper.UserMapper; import com.augus.pojo.User; import com.augus.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; //根据用户名查询用户信息 @Override public User findByUserName(String uname) { return userMapper.findByUserName(uname); } }
5.4.新建interceptor包
创建拦截器 LoginInterceptor,代码如下:
package com.augus.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class LoginInterceptor implements HandlerInterceptor { /** * 目标方法执行前 * * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /*设置请求和响应的乱码 */ request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); // 获取请求的URL /* * indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。 * 如果没有找到匹配的字符串则返回 -1。*/ String url = request.getRequestURI(); // login.jsp或登录请求放行,不拦截 if (url.indexOf("/toLogin") >= 0 || url.indexOf("/login") >= 0) { return true; } // 获取 session HttpSession session = request.getSession(); Object obj = session.getAttribute("loginUser"); //不等于null表示是已经登录了 if (obj != null) return true; // 没有登录且不是登录页面,转发到登录页面,并给出提示错误信息 request.setAttribute("msg", "还没登录,请先登录!"); /*request.getRequestDispatcher("/toLogin").forward(request, response); * 1、属于转发,也是服务器跳转,相当于方法调用,在执行当前文件的过程中转向执行目标文件,两个文件(当前文件和目标文件)属于同一次请求,前后页共用一个request,可以通过此来传递一些数据或者session信息,request.setAttribute()和request.getAttribute()。 * 2、在前后两次执行后,地址栏不变,仍是当前文件的地址。 * 3、不能转向到本web应用之外的页面和网站,所以转向的速度要快。 * 4、URL中所包含的“/”表示应用程序(项目)的路径。 * */ request.getRequestDispatcher("/toLogin").forward(request, response); //response.sendRedirect("login"); return false; } }
5.5.新建controller包
在项目创建LoginController,代码如下
package com.augus.controller; import com.augus.pojo.User; import com.augus.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @Controller public class LoginController { @Autowired private UserService userService; @RequestMapping("toLogin") public String userLogin(){ return "login"; } @RequestMapping("/login") public String login(User user, HttpServletRequest request){ //获取根据用户名 查询到的user对象 User user2 = userService.findByUserName(user.getUsername()); if(user2 != null && user2.getPassword().equals(user.getPassword())){ //如果byUserName不为空,输入的密码和数据库中保存的密码保持一致,则表示登录成功,就写出产品信息 HttpSession session = request.getSession();//将用户信息放到session域中 //将当前登录的user2对象放到session域中 session.setAttribute("loginUser", user2); return "redirect:/main"; } request.setAttribute("msg","账户或者密码错误!!!"); return "login"; } /** * 跳转到主页面 */ @RequestMapping("/main") public String toMain() { return "main"; } //退出 @RequestMapping("/logout") public String Logout(User user, HttpServletRequest request) { //session 失效 request.getSession().invalidate(); return "login"; } }
六、在WEB-INF下面创建前端html文件
- 创建login.html,代码如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <form action="login" method="post"> <table style="margin: auto"> <tr> <td> <p style="color: red;margin: auto">${requestScope.message}</p> </td> </tr> <tr> <td>用户名:</td> <td><input type="text" name="username" required></td> </tr> <tr> <td>密码:</td> <td><input type="text" name="password" required></td> </tr> <tr> <td colspan="2" align="center"><input type="submit" value="登陆"></td> </tr> </table> </form> </body> </html>
- 创建main.html,代码如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>欢迎来到主页</title> </head> <body> <h1>欢迎您:${sessionScope.loginUser.username}</h1> <a href="logout">退出登录</a> </body> </html>
七、启动Tomcat重新部署项目
访问http://localhost:8080/ssm02_war_exploded/mian,效果如下:
从上图可以看出,当用户没有登录而直接访问系统主页面(mian.html)时请求将被登录拦截器拦截,返回到登录页面,并提示信息。如果用户在用户名框中输入“zhangsan”,在密码框中输入“123456”,单击“登录”按钮后浏览器的显示结果如下图
当单击“退出”链接后,系统将从主页面返回到登录页面。
如果输入的用户名或密码错误,浏览器的显示结果如下图所示: