目录
前言
实战开发:
一、Spring Security整合到SSM项目
1. pom文件引入包
2. web.xml 配置
3. 添加 spring-security.xml 文件
二、Spring Security实战应用
1. 项目结构
2. pom文件引入
3. web.xml 配置
4. Spring 配置 applicationContext.xml
5. spring-security.xml 配置
6. springmvc.xml 配置
7. 创建实体类
8. DAO层实现数据查询
9. SystemDao 接口编写(数据层接口类)
10. SystemService接口及实现类SystemServiceImpl编写
11. SystemController 控制器编写
12. SpringSecurity实战讲解
13. 运行项目查看效果
前言
实战前提条件:
基础的SSM项目已集成完毕。在此基础上集成Spring Security实现web项目的安全保护 。
本文版本说明:
JDK:1.8
spring.version:5.2.12.RELEASE
Spring Security.version:4.2.5.RELEASE
Spring Security标签库:4.2.3.RELEASE
实战目标:
Authentication:认证,实现用户认证登录
Authorization:授权,设定用户的资源,访问权限。
实战开发:
一、Spring Security整合到SSM项目
1. pom文件引入包
<!-- Spring Security,此处引入4.2.5.RELEASE版本。 因为spring security 5.X版本需要提供一个PasswordEncorder的实例,否则后台会报错。 当然你也可以提供PasswordEncorder的实例 使用5.X版本--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.5.RELEASE</version> </dependency> <!-- Spring Security 标签库--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.3.RELEASE</version> </dependency>
注:本项目完整的pom文件稍后附上
2. web.xml 配置
注:先说说web.xml配置文件中Spring家族的加载顺序。
先启动spring ioc容器 --> 再启动spring-security --> 然后启动springmvc
<!-- https://blog.csdn.net/qyb19970829/article/details/110100544 配置时注意关于spring容器加载顺序的问题, applicationContext.xml,spring-security.xml,springmvc.xml 即这几个配置文件加载顺序 --> <!-- SpringSecurity过滤器链 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置Spring的监听器,启动spring容器--> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--配置加载类路径的配置文件,注意加载顺序 先加载spring--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring/applicationContext.xml <!-- Spring配置文件 --> classpath:spring/spring-security.xml <!-- SpringSecurity配置文件 --> </param-value> </context-param>
注:本项目完整的web.xml配置稍后附上
3. 添加 spring-security.xml 文件
注:本文件使用form-login的方式进行认证,在项目resources文件下新建spring文件夹(如果没有的话)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!--spring过滤器链配置 1) 需要拦截什么资源 2) 什么资源对应什么角色权限 3) 定制认证方式: HttpBasic or FormLogin 4) 自定义登录页面,定义登录请求地址,定义错误处理方式 --> <security:http> <!-- 使用http-basic的方式进行认证 --> <!--<security:http-basic/>--> <!-- 使用form-login的方式进行认证 --> <security:form-login/> <!-- 配置资源拦截规则 pattern属性指定资源目录: 即需要拦截的资源 /* 代表根目录下的一级目录 /** 代表根目录下的所有目录 access(SpEL)方法执行Spring EL表达式。提供如下表达式: permitALL():设置那些路径可以直接访问,不需要认证。直接返回true isAnonymous():只有匿名用户可以访问,登录用户不可访问 isAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户,则返回true,认证通过 isFullyAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户或记住我的用户,则返回true,认证通过 其它自行查找...... --> <security:intercept-url pattern="/**" access="isAuthenticated()"/> </security:http> <!--身份验证管理器--> <security:authentication-manager> <!--自定义授权提供源,实际开发中提供 自定义用户详情查询获取接口--> <security:authentication-provider> <security:user-service> <security:user name="admin" password="123456" authorities="ROLE_USER"/> </security:user-service> </security:authentication-provider> </security:authentication-manager> </beans>
以上为spring-security.xml 的基础配置,到此Spring Security整合到SSM项目中已经完毕!运行项目后,所有资源会被拦截,跳转到默认登录页要求用户进行登录认证后才能访问项目资源。如图:
二、Spring Security实战应用
1. 项目结构
2. pom文件引入
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.wqbr</groupId> <artifactId>wqdemotwo</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <name>wqdemotwo Maven Webapp</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.2.12.RELEASE</spring.version> </properties> <dependencies> <!--spring 包--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <!-- Spring Security,此处引入4.2.5.RELEASE版本。 因为spring security 5.X版本需要提供一个PasswordEncorder的实例,否则后台会报错。 当然你也可以提供PasswordEncorder的实例 使用5.X版本--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.5.RELEASE</version> </dependency> <!-- Spring Security 标签库--> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.3.RELEASE</version> </dependency> <!-- 引入jackson依赖包--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.4</version> </dependency> <!--JSP(Java Server Pages,Java服务端页面)--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <!-- JSTL标准标签库(Jsp Standarded Tag Library),使用标签取代JSP里的JAVA代码 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!--servlet API--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <!-- junit测试包 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--mybatis 相关包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.5</version> </dependency> <!--mybatis和spring集成的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <!--oracle JDBC连接依赖--> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>21.3.0.0</version> </dependency> <dependency> <groupId>cn.easyproject</groupId> <artifactId>orai18n</artifactId> <version>12.1.0.2.0</version> </dependency> <!--阿里的连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> </dependencies> <build> <finalName>wqdemotwo</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1.1</version> <executions> <execution> <id>attach-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> <configuration> <attach>true</attach> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <includeEmptyDirectories>true</includeEmptyDirectories> </configuration> </plugin> </plugins> </build> </project>
3. web.xml 配置
配置web.xml,加载spring(applicationContext.xml --spring默认配置文件),加载spring-security,加载springmvc。
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <!-- https://blog.csdn.net/qyb19970829/article/details/110100544 配置时注意关于spring容器加载顺序的问题, applicationContext.xml,spring-security.xml,springmvc.xml 即这几个配置文件加载顺序 --> <!--字符编码过滤器一定要放在前面--> <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>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--不拦截所有是html的页面请求,weblogic中部署去掉 <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping>--> <!-- SpringSecurity过滤器链 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--配置Spring的监听器,启动spring容器--> <!--配置加载类路径的配置文件,注意加载顺序--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:spring/applicationContext.xml classpath:spring/spring-security.xml </param-value> </context-param> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--配置前端控制器,对浏览器发送的请求进行统一处理--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!--加载springmvc.xml配置文件的位置和名称,配置的是Spring配置--> <init-param> <!--contextConfigLocation:上下文配置路径,固定值--> <param-name>contextConfigLocation</param-name> <!--classpath:类路径,指的是Java和resources文件夹--> <!--springmvc.xml:指的是配置文件的名称:需要配置springmvc.xml,在下面。 spring默认配置文件为applicationContext.xml。当中配置spring创建容器时要扫描的包 已经整合到springmvc.xml中--> <param-value> classpath:spring/springmvc.xml </param-value> </init-param> <!--配置启动加载--> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--开启项目时打开的页面--> <!-- <welcome-file-list> <welcome-file>/index.html</welcome-file> </welcome-file-list>--> </web-app>
4. Spring 配置 applicationContext.xml
指定扫描包,数据源,整合集成接管mybatis。
<?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/aop" 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-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"> <!--配置spring创建容器时要扫描的包--><!--同时也是 MyBatis托管的包路径--> <!-- 扫描除了controller的所有bean 这里一定要 use-default-filters="true"--> <context:component-scan base-package="com.wqbr" use-default-filters="true"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 引入配置文件--> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:spring/jdbc.properties" /> </bean> <!--创建数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean> <!--<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>testJNDI</value> </property> </bean>--> <!--创建sqlSessionFactory,接管了mybatis配置文件。整合Mybatis--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" > <array> <!--映射在class编译路径下的TblsicardDao.xml全路径--> <value>classpath:mapping/SystemDao.xml</value> </array> </property> </bean> <!--创建DAO,扫描mybatis接口的实现,加入到ioc容器中--> <bean id="systemDao" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> <property name="mapperInterface" value="com.wqbr.persistence.SystemDao"/> </bean> </beans>
5. spring-security.xml 配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:security="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!--spring过滤器链配置 1) 需要拦截什么资源 2) 什么资源对应什么角色权限 3) 定制认证方式: HttpBasic or FormLogin 4) 自定义登录页面,定义登录请求地址,定义错误处理方式 --> <security:http> <!-- 使用form-login的方式进行认证 login-page:指定获取登录页面的url(需要编写controller返回登录页面) login-processing-url:指定登录页面中post请求提交到哪里的url(不需要编写controller,框架已实现) default-target-url:指定登录成功后,跳转到哪个url(需要编写controller) authentication-success-handler-ref:指定登录成功后,由哪个类来进行处理 authentication-failure-handler-ref:指定登录失败后,由哪个类来进行处理 username-parameter:指定登录表单中用户名的input中name值,如果这里不配置,则默认为username password-parameter:指定登录表单中密码的input中name值,如果这里不配置,则默认为password --> <security:form-login login-page="/login" login-processing-url="/spring_security_check" authentication-success-handler-ref="myAuthenticationSuccessHandler" authentication-failure-handler-ref="myAuthenticationFailureHandler"/> <!-- 关闭csrf的保护--> <security:csrf disabled="true"/> <!-- 配置资源拦截规则 pattern属性指定资源目录: 即需要拦截的资源 /* 代表根目录下的一级目录 /** 代表根目录下的所有目录 access(SpEL)方法执行Spring EL表达式。提供如下表达式: permitALL():设置那些路径可以直接访问,不需要认证。直接返回true isAnonymous():只有匿名用户可以访问,登录用户不可访问 isAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户,则返回true,认证通过 isFullyAuthenticated():需要身份认证成功才能访问。如果认证用户不是匿名用户或记住我的用户,则返回true,认证通过 其它自行查找...... --> <!--开始配置拦截规则,注意拦截规则的位置顺序(如不需要身份认证的规则,要放在前面,需要身份认证的规则放在后面)--> <!--permitAll()不需要身份认证,无条件放行--> <security:intercept-url pattern="/login" access="permitAll()"/> <security:intercept-url pattern="/system/index" access="permitAll()"/> <!--进行权限划分:hasRole('ROLE_USER'):表示拥有 ROLE_USER 权限的用户可以访问 hasRole('ROLE_ALL'):表示拥有 ROLE_ALL 权限的用户可以访问 --> <security:intercept-url pattern="/system/add" access="hasAuthority('admin')"/> <security:intercept-url pattern="/system/list" access="hasAuthority('ROLE_ALL')"/> <!--permitAll()不需要身份认证,无条件放行静态资源--> <security:intercept-url pattern="/js/**" access="permitAll()"/> <!--拦截所有页面,需要身份认证成功才能访问。如果认证用户不是匿名用户或记住我的用户,则返回true,认证通过--> <security:intercept-url pattern="/**" access="isFullyAuthenticated()"/> <!--结束配置拦截规则--> <!-- 自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面) --> <security:access-denied-handler error-page="/accessDeny"/> <!--加上Remember Me功能,token-validity-seconds:有效时间(秒)--> <!--<security:remember-me token-repository-ref="jdbcTokenRepository" token-validity-seconds="604800"/>--> <!--<security:logout/>:注销功能 logout-url="/logout":springSecurity内LogoutFilter要拦截的url(向这个url发送请求来注销) logout-success-url:用户退出后要被重定向的url invalidate-session:默认为true,用户在退出后Http session失效 success-handler-ref:指定一个bean(需要实现LogoutSuccessHandler接口),用来自定义退出成功后的操作--> <security:logout logout-url="/logout" logout-success-url="/login" invalidate-session="true"/> </security:http> <!--身份验证管理器--> <security:authentication-manager> <!-- 自定义授权提供类MyUserDetailsService,获得登录用户的用户详情信息。此类实现UserDetailsService接口。 user-service-ref="myUserDetailsService" : 指定 UserDetailsService 接口的实现类 最终都要返回一个UserDetail,用户详情 --> <security:authentication-provider user-service-ref="myUserDetailsService"> <!-- 配置:加密算法对用户输入的密码进行加密,然后和数据库的密码进行配对 --> <!--<security:password-encoder ref="bCryptPasswordEncoder"/>--> </security:authentication-provider> </security:authentication-manager> <!--创建 springSecurity 密码加密工具类,使用PasswordEncoder 接口的实现,也可以使用别的--> <!--<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>--> <!--springSecurity实现 remember me 功能: 如果用户登录选择 remember me ,springSecurity会将其cookie值存入数据库,来实现remember me 功能 JdbcTokenRepositoryImpl 用来存取cookie值--> <!--<bean id="jdbcTokenRepository" class="org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl"> <property name="dataSource" ref="dataSource"/> <!–数据库数据源–> <!–<property name="createTableOnStartup" value="true"/>–> <!–createTableOnStartup属性是当项目启动时,springSecurity创建表存储remember me相关信息,第二次启动时要注释这个属性–> </bean>--> </beans>
6. springmvc.xml 配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--配置spring创建容器时要扫描的包--> <!-- 禁用默认扫描规则,use-default-filters="false"--> <context:component-scan base-package="com.wqbr" use-default-filters="false"> <!--只扫描Controller注解的类--> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--处理映射器--> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/> <!--处理器适配器--> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/> <!--配置JSP视图解析器--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/"></property> <!--规定跳转页面路径的前缀--> <property name="suffix" value=".jsp"></property> <!--规定跳转页面的后缀--> </bean> <!-- 配置spring开启注解mvc的支持 默认就是开启的 ,要想让其他组件(不包含映射器、适配器、处理器)生效就必须需要配置了--> <mvc:annotation-driven/> <!-- 让默认servlet处理静态资源。将springMVC不能处理的请求交给servlet,一般用来放行静态资源 --> <mvc:default-servlet-handler/> </beans>
7. 创建实体类
注:重点在SysUser实体类。实现UserDetails接口 复写接口的方法进行实现,建立各方法的对应属性到用户表中(不一定全建对应属性)。
我们先来看下UserDetails接口类的源码:
红色标注的3项建立在用户实体领域类中,如下SysUser 用户实体类代码:
package com.wqbr.domain; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; /** * 系统用户,封装用户数据,实现 UserDetails 接口 * @author lv * @date 2024年1月11日 */ public class SysUser implements UserDetails { private static final long serialVersionUID = 1L; private String id; private String username; //从UserDetails的重写方法中返回 private String password; //从UserDetails的重写方法中返回 private Date addtime; private boolean accountnonexpired; //账户是否过期,从UserDetails的重写方法中返回 private boolean accountnonlocked; //账户是否锁定,从UserDetails的重写方法中返回 private boolean credentialsnonexpired; //密码是否过期,从UserDetails的重写方法中返回 private boolean enabled; //账户是否可用,从UserDetails的重写方法中返回 // 储存用户拥有的所有权限 private List<GrantedAuthority> authorities = new ArrayList<>(); //从UserDetails的重写方法中返回 public String getId() { return id; } public void setId(String id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public Date getAddtime() { return addtime; } public void setAddtime(Date addtime) { this.addtime = addtime; } // 返回用户权限,上面声明了权限集合对象 authorities @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authorities; } public void setAuthorities(List<GrantedAuthority> authorities) { this.authorities = authorities; } // 返回用户密码,上面声明了属性 password @Override public String getPassword() { return password; } // 返回用户名,上面声明了属性 username @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return accountnonexpired; } public void setAccountnonexpired(boolean accountnonexpired) { this.accountnonexpired = accountnonexpired; } @Override public boolean isAccountNonLocked() { return accountnonlocked; } public void setAccountnonlocked(boolean accountnonlocked) { this.accountnonlocked = accountnonlocked; } @Override public boolean isCredentialsNonExpired() { return credentialsnonexpired; } public void setCredentialsnonexpired(boolean credentialsnonexpired) { this.credentialsnonexpired = credentialsnonexpired; } @Override public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } }
注:private List<GrantedAuthority> authorities = new ArrayList<>(); 此属性稍后赋值演示
角色(SysRole )和资源(SysPermission)实体类代码参见以下文章建立: spirng框架之spring security(二)insert 语句补充-CSDN博客https://blog.csdn.net/u011529483/article/details/135467110?spm=1001.2014.3001.5501
8. DAO层实现数据查询
SystemDao.xml,mybatis的mapper文件(映射SQL语句)
<?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"> <!--命名空间映射到com.wqbr.persistence.SystemDao 类--> <mapper namespace="com.wqbr.persistence.SystemDao"> <!--SysUser findByUsername(String username);方法的映射对应id="findByUsername",resultType返回类型为SysUser实体类--> <select id="findByUsername" parameterType="String" resultType="com.wqbr.domain.SysUser"> select * from SYS_USER where USERNAME = #{username} </select> <!--查询当前用户拥有的资源--> <select id="findPermissionByUsername" parameterType="String" resultType="com.wqbr.domain.SysPermission"> select d.* from sys_user a, sys_user_role b, sys_role_permission c, sys_permission d where a.id = b.user_id and b.role_id = c.role_id and c.permission_id = d.id and a.username = #{username} </select> </mapper>
9. SystemDao 接口编写(数据层接口类)
package com.wqbr.persistence; import com.wqbr.domain.SysPermission; import com.wqbr.domain.SysUser; import java.util.List; public interface SystemDao { /** * 查询当前用户对象 */ public SysUser findByUsername(String username); /** * 查询当前用户拥有的资源 */ public List<SysPermission> findPermissionByUsername(String username); }
10. SystemService接口及实现类SystemServiceImpl编写
package com.wqbr.service; import com.wqbr.domain.SysPermission; import java.util.List; /** * 系统服务接口 * @author lv * @date 2024年1月11日 */ public interface SystemService { /** * 查询当前用户拥有的资源 */ public List<SysPermission> findPermissionByUsername(String username); }
package com.wqbr.service.impl; import com.wqbr.domain.SysPermission; import com.wqbr.persistence.SystemDao; import com.wqbr.service.SystemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * 系统服务接口实现 * @author lv * @date 2024年1月11日 */ @Service public class SystemServiceImpl implements SystemService { @Autowired private SystemDao systemDao; @Override public List<SysPermission> findPermissionByUsername(String username) { return systemDao.findPermissionByUsername(username); } }
11. SystemController 控制器编写
package com.wqbr.controller; import com.wqbr.domain.Menus; import com.wqbr.domain.SysPermission; import com.wqbr.domain.SysUser; import com.wqbr.service.SystemService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.List; /** * 系统用户控制器 * @author lv * @date 2024年1月11日 */ @Controller @RequestMapping("/system") public class SystemController { /** * 自动装配SystemService接口 */ @Autowired private SystemService systemService; /** * 处理超链接发送出来的请求 * @param model * @return */ @RequestMapping(path = "/hello") public String sayHello(Model model){ System.out.println("入门方法执行了2..."); // 配置了视图解析器后,写法 return "main/index"; } @RequestMapping(path = "/haa") public String haa(Model model){ System.out.println("haa *****bb 2 999999999999999999..."); // 向模型中添加属性msg与值,可以在html页面中取出并渲染 //model.addAttribute("msg","hello,SpringMVC"); // 配置了视图解析器后,写法 return "main/index"; } @RequestMapping(path = "/index") public String index(){ System.out.println("index 页面进入......"); return "main/index"; } @RequestMapping(path = "/list") public String list(){ System.out.println("list方法进入......"); return "main/list"; } @RequestMapping(path = "/add") public String add(){ System.out.println("add方法进入......"); return "main/add"; } @GetMapping("/findMenu") public ModelAndView findMenus(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { ModelAndView model = new ModelAndView("main/menu"); SysUser user = (SysUser) authentication.getPrincipal(); String username=user.getUsername(); if(username!=null){ List<Menus> listMenu = new ArrayList<>(); List<SysPermission> pList = systemService.findPermissionByUsername(username); System.out.println("=-----=大小为:"+pList.size()); for (SysPermission permission : pList) { if (permission.getResource_type().equals("menu")) { Menus menu = new Menus(); menu.setId(Long.parseLong(permission.getId())); menu.setName(permission.getName()); menu.setParentId(Long.parseLong(permission.getParent_id())); menu.setParentIds(Long.parseLong(permission.getParent_ids())); menu.setUrl(permission.getUrl()); listMenu.add(menu); } } //request.setAttribute("listMenus", listMenu); model.addObject("listMenu",listMenu); // for (Menus menus : listMenu) { // if (menus.getParentId() == 10000) { //10000为数据库中的值 // System.out.println("==" + menus.getName() + "[" + menus.getUrl() + "]"); // for (Menus menusch : listMenu) { // if (menus.getId() == menusch.getParentId()) { // System.out.println("---------" + menusch.getName() + "[" + menusch.getUrl() + "]"); // } // } // } // } } return model; } }
12. SpringSecurity实战讲解
现在请查看之前配置的spring-security.xml文件。
如图 spring-security.xml 文件中给出了 security:form-login 的4个属性。并禁用了 csrf 。且指定了 error-page 的路径。所以需要编写 Controller 实现 login-page 及 error-page。
- LoginController 控制器编写
package com.wqbr.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * 系统用户控制器 * @author lv * @date 2024年1月16日 */ @Controller public class LoginController { @RequestMapping("/login") public String login(){ System.out.println("初始 指定 进入登录页面!。。。。。。。。。。。。。。"); return "login"; } /** * 自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面) * @return */ @RequestMapping("/accessDeny") public String accessDeny(){ System.out.println("自定义用户访问权限不足的处理方式(需要编写controller返回权限不足的页面)。。。。。。。。。。。。。。"); return "accessdeny"; } }
- 编写 MyAuthenticationSuccessHandler 与 MyAuthenticationFailureHandler 类实现spring-security.xml 文件中 authentication-success-handler-ref 与 authentication-failure-handler-ref 属性指定的接口。(以json格式返回成功或失败)
package com.wqbr.service.impl; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Service; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Service public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { // new 一个 jackson 的 对象 private ObjectMapper objectMapper = new ObjectMapper(); /** * 此方法会在登录成功后进行回调 * * @param authentication:表示认证成功后的信息 */ @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { // 自己构造json字符串,返回给前端 Map<String,Object> result = new HashMap<>(); result.put("authStr", "success"); String json = objectMapper.writeValueAsString(result); // 使用response设置响应头为JSON httpServletResponse.setContentType("text/json;charset=utf-8"); // 回写数据 httpServletResponse.getWriter().write(json); } }
package com.wqbr.service.impl; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Service; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; @Service public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { // new 一个 jackson 的 对象 private ObjectMapper objectMapper = new ObjectMapper(); /** * 此方法会在登录失败后进行回调 * * @param authenticationException:表示认证失败后的信息 */ @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException { // 自己构造json字符串,返回给前端 Map<String,Object> result = new HashMap<>(); result.put("authStr", "fail"); String json = objectMapper.writeValueAsString(result); // 使用response设置响应头为JSON httpServletResponse.setContentType("text/json;charset=utf-8"); // 回写数据 httpServletResponse.getWriter().write(json); } }
- 编写login.jsp页面,登录提交路径为 spring-security.xml 中指定的 login-processing-url="/spring_security_check" 路径。且运行项目后会根据 spring-security.xml 中的 login-page="/login" 访问 LoginController 控制器的方法跳转到 login.jsp 页。
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %> <%@ page isELIgnored="false" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>登录页面</title> </head> <body> <h3>用户登录</h3> <form action="${pageContext.request.contextPath}/spring_security_check" method="post"> 用户名:<input type="text" name="username"/><br> 用户密码:<input type="password" name="password"/><br> <input type="submit" value="登 录"/> </form> </body> </html>
- spring-security.xml文件中的拦截规则如图:
- spring-security.xml文件中的用户详情接口实现(自定义登录授权实现类)
MyUserDetailsService类实现UserDetailsService接口,重写loadUserByUsername方法,实现此方法(用户登录时调用此方法,通过用户输入的登录信息查找数据库用户表进行身份认证匹配)
package com.wqbr.service.impl; import com.wqbr.domain.SysPermission; import com.wqbr.domain.SysUser; import com.wqbr.persistence.SystemDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; /** * 系统用户控制器 * @author lv * @date 2024年1月16日 */ @Service public class MyUserDetailsService implements UserDetailsService { @Autowired private SystemDao systemDao; /** * loadUserByUsername:读取用户信息 * 返回值类型 UserDetails 是 SpringSecurity 用来封装用户数据的接口 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("-------loadUserByUsername方法加载中。。。。username:"+username); /** * name: 用户名 * password: 密码 * authorities: 定义权限名称 */ // User user = new User("admin", "123456", // AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal,ROLE_ALL")); // return user; SysUser user=systemDao.findByUsername(username); System.out.println("user-====="+user); //判断 if(user!=null){ System.out.println(user.getUsername()+"---====---"+user.getPassword()+"----"+user.getAddtime()); /* List<SysPermission> permList=systemDao.findPermissionByUsername(user.getUsername()); StringBuffer sb = new StringBuffer(); for (SysPermission sysPermission : permList) { sb.append(sysPermission.getUrl()); sb.append(","); } sb.delete(sb.length()-1,sb.length());*/ List<GrantedAuthority> list=AuthorityUtils.createAuthorityList("ROLE_USER","admin"); user.setAuthorities(list); //设置权限列表 return user; } throw new UsernameNotFoundException(username+"用户名不存在!"); } }
到此spring-security讲解完毕,接下来补全几个测试页面
accessdeny.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> 用户访问权限不足!。。。。。。。。。。。。。。。。。。。。。。。 </body> </html>
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %> <%@ page isELIgnored="false" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>welcome!</title> </head> <body> <h3>welcome! index页面</h3> <form action="#" method="post"> </form> </body> </html>
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h2>add...! 内测页面 </h2> </body> </html>
list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h2>list...! 列表 显示 </h2> <h3>list...! || 列表 显示 </h3> </body> </html>
13. 运行项目查看效果
登录
输入错误的用户名、密码
输入正确的用户名、密码
登录成功后尝试访问/system/add 和 /system/list 方法请求
如图 /system/add 成功访问,/system/list 无法访问,因为权限不足。如下图用户详情类中没有给用户指定 ROLE_ALL 权限
现在我们关闭浏览器,重新打开浏览器。不登录的情况下访问控制器的 /system/add 请求 和 /system/index 请求。
访问 http://localhost:8080/wqdemotwo_war/system/add
跳回到登录页面。
访问 http://localhost:8080/wqdemotwo_war/system/index
成功访问。
好了关于 spring-security 就结束了。
下一篇讲讲用户认证登录进来以后如何根据角色获取菜单资源