目录
4. Spring?配置?applicationContext.xml
10. SystemService接口及实现类SystemServiceImpl编写
实战前提条件:
基础的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,此处引入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>
注:先说说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配置稍后附上
注:本文件使用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项目中已经完毕!运行项目后,所有资源会被拦截,跳转到默认登录页要求用户进行登录认证后才能访问项目资源。如图:
<?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>
配置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>
指定扫描包,数据源,整合集成接管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>
<?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>
<?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>
注:重点在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
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>
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);
}
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);
}
}
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;
}
}
现在请查看之前配置的spring-security.xml文件。
如图?spring-security.xml 文件中给出了 security:form-login 的4个属性。并禁用了 csrf 。且指定了 error-page 的路径。所以需要编写?Controller 实现 login-page 及 error-page。
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";
}
}
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);
}
}
<%@ 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>
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>
登录
输入错误的用户名、密码
输入正确的用户名、密码
登录成功后尝试访问/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 就结束了。
下一篇讲讲用户认证登录进来以后如何根据角色获取菜单资源