SpringSecurity框架 —— 安全校验
前言:
随着技术的不断发展和完善,极大的方便了我们的生活和工作。但也存在着很大的安全隐患,例如:黑客的攻击。如果没有一定的安全防护措施 将会带来巨大的 业务损失。安全访问控制 特别是大型企业 都非常重视的一个环节。
1. Spring Security 的入门介绍
Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式 的安全访问控制解决方案 的安全框架。它提供了一组可以在 Spring 应用上下文中配置 的 Bean,充分利用了 Spring IoC(控制反转),DI(依赖注入) 和 AOP(面向切面编程) 的功能,为应用系统提供 声明式的安全访问控制功能,减少了为 企业系统安全控制 编写大量重复代码 的工作。
Spring Security 是一种 声明式的安全框架 即:一种配置方式的框架。所有的安全框架都是基于 filter 做的,因为 过滤器可以去拦截所有的请求。Spring Security 有十几个 filter 。当然还有一种安全框架 Shiro ,这款在市面上也是用的挺多的,这里只做对 Spring Security 进行介绍。如果不做安全校验,项目中的所有页面都是对外直接公开可访问的,安全隐患 很大。
Spring Security 共有四种用法:
① 不用数据,所有的数据全部写在配置文件里面,这也是官方提供的 demo (下面的案例也采用这种方法);
② 使用数据库,但这种方法使用的数据库是固定死的,不够灵活,因要根据 Spring security 默认实现代码设计数据库;
③ Spring Security 与 Acegi 有所不同,它不能修改默认的filter,只能添加新的filter;
④ 暴力手段 修改源码,这种方法 不可取,不实际。
2. 下面通过 demo 详细介绍 Spring Security 的使用 和 配置信息
2.1 创建一个maven (war) 工程
2.1.1 简单演示
(1) 添加pom 依赖
<properties>
<spring.version>4.2.4.RELEASE</spring.version>
</properties>
<dependencies>
<!-- spring 相关的 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</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-context-support</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-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- security 相关的 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>9090</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
(2) web.xml 文件配置
注意:Security 是有十几个filter的,下面的 配置里面 我们使用的是 filterChain 是一个过滤器链,使用这一个即可。
<?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"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 通过监听 加载spring 配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 拦截器的固定写法 name不能随便改动,并且springSecurityFilterChain 是一个过滤器链
springSecurityFilterChain 会在 spring-security.xml 配置文件中创建一个对应的Bean
-->
<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>
</web-app>
(3) security 所需要的 resources目录下的 .xml 的配置文件 spring-security.xml 文件
注意:xmlns 是命名空间,在下面的配置中是默认的,而 xmlns:beans 不是默认的,所以在创建 Bean 的时候 使用的标签是<beans:bean>
Spring Security 的两个功能:认证 和 授权。认证,说的就是登陆 即:谁在登录;授权,说的是登录后 能做什么,或者能访问哪些页面等,即:登录后 必须拥有相应的角色 做相应的事。
①<Http> 标签参数介绍:
pattern:拦截的路径,如果没有登录就直接拦截,不让访问页面
/*:只拦截当前目录(webapp)下的文件,当前目录下的文件夹里面文件不能被拦截
/**:拦截当前目录(webapp)下以及当前目录下文件夹里面的文件。(存在递归)
access: 登录之后所给的权限,登录成功后 也要有相应的权限才能访问页面
两种写法:
①http中有一个熟悉:use-expressions 默认是true 使用表达式,那么access必须写成hasRole()
②use-expressions 改为false 那么access=ROLE_USER
<form-login>: 如果没有登录页面,此标签会提供一个默认的登录页面,输入地址:localhost:9090
② 认证管理器 authorities: 指定当前用户 所拥有的权限
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" 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">
<!-- 拦截规则 -->
<http use-expressions="true"><!-- 如果是true, use-expressions可以省略不写-->
<intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>
<!-- 如果没有写登录页面,form-login 会提供一个默认的登录页面 -->
<form-login/>
</http>
<!-- 认证管理器 -->
<authentication-manager>
<authentication-provider>
<user-service>
<!-- 指定哪个用户进行登录 这种方式是写死的,应该是去数据库里查找
authorities:指定当前用户 有哪些权限
-->
<user name="admin" password="admin" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
(4) 创建 页面 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
欢迎来到神奇的 Security 世界
</body>
</html>
(5) 运行 测试 — — 需要注意的情况如下:
浏览器中输入地址: localhost:9090/index.html ,会被自动拦截跳转到 localhost:9090/login 页面
情况一:如果登录失败 就会自动跳转的一个错误页面
情况二:当登录成功后,会报一个错误,说是 找不到 favicon.ico,其实就是一个小图标,例如打开百度网页后,在标题栏上"百度一下,你就知道 " 前面的那个 小 爪印的图标。
解决方案: 网上找一个 放到 webapp 目录下面就 OK 了 。
此时在访问 localhost:9090/index.html 即可成功进入页面
情况三:如果 认证管理器中的权限 与 拦截器里面的权限 不一致,会出现 可以登录 但是却无法访问 网页
例如:access="hasRole('ROLE_USER')" 而 authorities="ROLE_ADMIN" 此时 权限不一致,就会出现下面情况:
2.1.2 升级演示
情 景 一:
(1) 角色的使用演示
① 修改 spring-security.xml 文件,如下: 三个拦截规则 ,三个用户。演示效果:对应的用户只能访问对应规则下的页面。
拦截规则修改:三个规则 及对应三个页面
<http><!-- 如果是true, use-expressions可以省略不写-->
<intercept-url pattern="/index1.html" access="hasRole('ROLE_USER1')"/>
<intercept-url pattern="/index2.html" access="hasRole('ROLE_USER2')"/>
<intercept-url pattern="/index3.html" access="hasRole('ROLE_USER3')"/>
<!-- 如果没有写登录页面,form-login 会提供一个默认的登录页面 -->
<form-login/>
</http>
② 认证管理器修改:三个 用户 对应三个 规则
<!-- 认证管理器 -->
<authentication-manager>
<authentication-provider>
<user-service>
<!-- 指定哪个用户进行登录 这种方式是写死的,应该是去数据库里查找
authorities:指定当前用户 有哪些权限
-->
<user name="admin1" password="admin" authorities="ROLE_USER1"/>
<user name="admin2" password="admin" authorities="ROLE_USER2"/>
<user name="admin3" password="admin" authorities="ROLE_USER3"/>
</user-service>
</authentication-provider>
</authentication-manager>
③ 修改 web.xml 配置 index1.xml,index2.xml,index3.xml 这三个页面,否则在访问 localhost:9093 的时候会报错
④ 运行 测试 ,如果是上次已登录 可输入 localhost:9093/login 即:退出上次登录,重新进行登录
登录后的显示结果:
如果现在输入地址:localhost:9093/index2.html 或者 localhost:9093/index3.html 是拒绝访问的,因为登录时是使用 admin1 进行的登录,没有访问 index2.html 和 index3.html 的权限。使用对应的权限才能访问对应的页面,上面的配置信息
情 景 二:
(2) <form-login/> 登录页面的完善
① 创建一个登录页面 login.html:
需要注意的是,① action 必须是 "/login" ;② method 必须是 "post" ;③ 用户名的 name 必须是 "usrename"
因为 <form-login/> 默认的登录就是 localhost:端口号/login。
② 修改 spring-security.xml 文件 给 <form-login/> 标签 指定自定义的登录页面
注意:"/login.html" 里面的 "/" 不能省略,它指向的是 项目路径
<form-login login-page="/login.html"/>
③ 启动服务,运行测试 地址栏: localhost:9093
情 景 三:
(3) 在上面的配置( spring-security.xml )中,因为我们只拦截了三个页面,实际业务开发中 这是不安全的,通常都是要把 "/**" 这个拦截级别添加上的,但是问题来了,如果 添加 "/**" 这个拦截级别 那么 login.html 这个自定义登录页 也访问不了了,这时候还需要一个 匿名访问配置(不进行校验拦截,使 /** 无效的配置 )。
① 追加 "/**" 拦截级别: <intercept-url pattern="/**" access="hasRole('ROLE_USER1')"/>
<!-- 拦截规则 -->
<http use-expressions="true">
<intercept-url pattern="/**" access="hasRole('ROLE_USER1')"/>
<intercept-url pattern="/index1.html" access="hasRole('ROLE_USER1')"/>
<intercept-url pattern="/index2.html" access="hasRole('ROLE_USER2')"/>
<intercept-url pattern="/index3.html" access="hasRole('ROLE_USER3')"/>
<!-- 如果没有写登录页面,form-login 会提供一个默认的登录页面
login-page: 指定自定义的登录页面
-->
<form-login login-page="/login.html"/>
</http>
② 添加 匿名访问 : 不进行校验拦截,
<!-- 匿名访问 -->
<http pattern="/login.html" security="none"></http>
③ 运行 测试
但是有个问题,当点击登录的时候 会报下面的一个错误,
HTTP Status 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
这个错误不是 访问拦截的错误,它是说 在你的 session 里面 没有找到 CSRF token
那么 问题来了,什么是 CSRF 呢? 它是 跨站请求伪造 即:一种 黑客的攻击手段。
跨站请求伪造 简介:
当我们通过自己的电脑 去访问自己的银行账户,这时候肯定要去连接银行的系统;那么如果在与银行系统保持连接的同时,我们又去访问了其他的页面,如果我们所访问的其他的页面里面包含一些攻击的代码,这些攻击代码就会模拟你的请求 继续向银行发送请求,模拟发送的这些请求如果只是简单的查询请求(get请求)是没问题的,如果进行一些操作修改(通常指post请求),此时银行系统是可以接受并处理的,因为银行系统以为是你本人在操作。这就是 跨站请求伪造。
解决跨站请求伪造攻击的方式:
方式一:短信验证码
攻击代码 在模拟你的请求的时候 是不知道你的验证码的。而你本人在发送请求的时候都是带着验证码的,银行系统就会根据你的验证码判断你的请求是否有效。但是如果每次请求都要发送一下验证码,使用效率也就大打折扣了,这种方式也比较少用。
方式二:security 的解决方案
security 会在你的页面上 得到一个 CSRF 的 token ,所谓的 token 就是令牌 即 随机字符串(本质还是验证码),这个验证码可以在你的服务器中生成 ,放到你的 session 里面,当你进行登录的时候 就可以把从页面得到的这个验证码 提交到后台 并进行校验。(目前我们的demo 仅仅是 html 的操作,服务器生成并页面提交这款可自行操作)通常是 jsp 用这个 token 会很方便(获取 session 数据时)。
此处我们 禁用 CSRF 即可。 在配置文件 spring-security.xml 中的 http 标签里面添加 <csrf disabled="true">
此时在进行登录,也就不会再出现上面的错误:
<csrf disabled="true"/>
④ 当我们在第三步中 添加了 自定义的登录页 后 ,我们不难发现,我们在使用 默认的登录页的时候 并没有进行CSRF 的校验检查,那这是为什么呢?
因为:CSRF 主要针对的就 post 请求,当我们使用默认的登录页的时候,它是自己的 from 表单 向自己提交,所以不需要进行校验;当使用自定义的 登录页去登录的时候 ,security 就认为这是 外部网站去访问的,当外部网站去访问的时候,并且还是 post 请求,这时候就需要去校验了。
情 景 四 :
(4) 当我们访问 index1.html 的时候,输入地址 :localhost:9093/index1.html 会被拦截到登录页面 :localhost:9093/login ,但是当我们登录成功之后 又会自动跳转到 localhost:9093/index1.html 页面。
那么问题来了,如何跳转到我们自己的指定的页面呢?
那就需要在 <form-login/> 标签里面指定两个属性:
① authentication-success-forward-url :指定登录成功之后 要跳转到的页面
② always-use-default-target :与 authentication-success-forward-url 配合使用,只有登录成功,就跳转到 ① 指定的路径。
对 spring-security.xml 的修改:都在<http> 标签中修改
添加 如下 配置信息:
<intercept-url pattern="/success.html" access="hasRole('ROLE_USER3')"/>
<!-- 如果没有写登录页面,form-login 会提供一个默认的登录页面
login-page: 指定自定义的登录页面
-->
<form-login login-page="/login.html"
authentication-success-forward-url="/success.html"
always-use-default-target="true"/>
情 景 五 :
(5) 有登录页面,当然也有退出后的页面,在 <http> 标签里面,添加 <logout/> 默认的是当成功退出后 直接跳转到登录页面( login.html ) 。
当然 他也可以指定退出后的自定义页面
属性:logout-success-url : 成功登出后 跳转到的页面
<logout logout-success-url="logout.html"/>
总结:Spring-Security.xml 的最终配置
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" 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">
<!-- 匿名访问 -->
<http pattern="/login.html" security="none"></http>
<!-- 拦截规则 -->
<http use-expressions="true">
<intercept-url pattern="/**" access="hasRole('ROLE_USER1')"/>
<intercept-url pattern="/index1.html" access="hasRole('ROLE_USER1')"/>
<intercept-url pattern="/index2.html" access="hasRole('ROLE_USER2')"/>
<intercept-url pattern="/index3.html" access="hasRole('ROLE_USER3')"/>
<intercept-url pattern="/success.html" access="hasRole('ROLE_USER1')"/>
<intercept-url pattern="/logout.html" access="hasRole('ROLE_USER1')"/>
<!-- 如果没有写登录页面,form-login 会提供一个默认的登录页面
login-page: 指定自定义的登录页面
-->
<form-login login-page="/login.html"
authentication-success-forward-url="/success.html"
always-use-default-target="true"/>
<csrf disabled="true"/>
<logout logout-success-url="logout.html"/>
</http>
<!-- 认证管理器 -->
<authentication-manager>
<authentication-provider>
<user-service>
<!-- 指定哪个用户进行登录 这种方式是写死的,应该是去数据库里查找
authorities:指定当前用户 有哪些权限
-->
<!-- <user name="admin" password="admin" authorities="ROLE_USER"/> -->
<user name="admin1" password="admin" authorities="ROLE_USER1"/>
<user name="admin2" password="admin" authorities="ROLE_USER2"/>
<user name="admin3" password="admin" authorities="ROLE_USER3"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
推荐阅读
-
SpringSecurity框架 —— 安全校验
-
springboot windows10风格 activiti 整合项目框架源码 shiro 安全框架 druid springboot
-
ibatis文档 博客分类: web框架 线程安全sqlmap新手入门注意事项mybatis
-
关于acegi安全框架登陆后转向的疑问 博客分类: open source Acegi框架JSPSpringSecurity
-
JavaWeb开发之Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架
-
JavaWeb开发之Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基础框架
-
spring-mvc整合shiro安全框架(身份认证)
-
spring-mvc整合shiro安全框架(权限拦截)
-
Spring自带的校验框架Validation的使用实例
-
[原创]实战Acegi:使用Acegi作为基于Spring框架的WEB应用的安全框架 Acegi框架WebSpringMySQL