spring security简介
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。 Spring Security该框架存在两个重要的核心功能: 认证和授权
# spring security入门案例
## 导入相关的依赖包
<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>
<!-- 引入spring 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>
<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>
## web.xml文件的配置
<!-- 配置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>
<!-- 配置spring security的委托过滤器代理 -->
<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-security.xml文件的配置
### 文件头配置
我们使用到了spring security就需要引入spring security的头信息配置,如下:
<?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/s ... spring-security.xsd">
</beans>
在这里我们把security的头信息的配置作为了默认配置,那么以后在进行编写security的相关配置的时候就不需要<security:xxx>前缀了,方便我们后期的代码书写!
### 文件内容
<!-- 页面的拦截规则 use-expressions:是否启动SPEL表达式 默认是true -->
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER"/> <!-- 当前用户必须有ROLE_USER的角色 才可以访问根目录及所属子目录的资源 -->
<form-login /> <!-- 开启表单登陆功能 -->
</http>
<!-- 认证管理器 -->
<authentication-manager>
<authentication-provider>
<user-service>
<user name="admin" password="123456" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
## 测试
访问<http://localhost:9090/>路径,发现我们请求的index.html页面被拦截了,跳转到了一个登陆页面,这个登录页面是由spring security给我们生成的!这是因为我们当时配置的拦截规则是/** 表示拦截所有的请求!
输入在spring-security.xml文件中配置的用户名和密码点击login按钮!登陆成功以后就可以看到我们的index.html中的内容!
spring security的配置介绍
## web.xml中配置的DelegatingFilterProxy
DelegatingFilterProxy类存在与spring-web包中,其作用就是一个filter的代理,用这个类的好处是可以通过spring容器来管理filter的生命周期,还有就是,可以通过spring注入的形式,来代理一个filter执行!DelegatingFilterProxy类继承GenericFilterBean,间接实现了Filter这个接口,故而该类属于一个过滤器。那么就会有实现Filter中init、doFilter、destroy三个方法。
1. 代理具体实现
首先我们看init方法,我们知道当filter初始化时会执行init方法,从源码中我们可以找到具体代码,该方法在GenericFilterBean类中实现,具体功能是,将该类封装成spring特有形式的类,方便spring维护,并且调用initFilterBean方法,该方法放在子类(DelegatingFilterProxy)实现,该方法主要目的是,找到在spring中维护的目标filter,具体实现看下面代码:
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'");
}
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
try {
// 将该类封装成spring特有的bean形式,方便spring维护
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
// 该方法在子类中实现,我们可以到DelegatingFilterPoxy中去看看,具体完成了那些工作?
//1、找到要代理bean的id--》targetBeanName
//2、在spring,bean容器中找到具体被代理的filter--》delegate
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully");
}
}
2. initFilterBean()该方法主要完成两个功能
1. 找到被代理类在spring中配置的id并赋值给targetBeanName
2. 使用找到的id从spring容器中找到具体被代理的类,并赋值给delegate
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// 找到要被代理的filter在spring中配置的id
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
// 找到具体被代理的filter
this.delegate = initDelegate(wac);
}
}
}
}
3. getFilterName()该方法的作用是,获取被代理的filter在spring中配置的id
protected final String getFilterName() {
// 找到被代理filter在spring中配置的id
return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName);
}
通过源码的查看,我们配置的DelegatingFilterProxy就是为了让其通过我们配置的filterName从spring容器中获取spring security对应的过滤器链.因此我们这个filterName需要是: springSecurityFilterChain 因为我们并没有在DelegatingFilterProxy过滤器中配置一些初始化参数!
## spring-security.xml文件
该文件为spring security的核心配置文件,在该文件中我们需要配置两部分的内容: 第一部分就是授权(通过<http>标签进行配置) , 第二部分就是认证(通过<authentication-manager>进行配置)
### 认证管理器的配置
<authentication-manager>
<authentication-provider>
<user-service>
<user name="admin" password="123456" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
authentication-manager 元素指定了一个 AuthenticationManager,其需要一个 AuthenticationProvider(对应 authentication-provider 元素)来进行真正的认证,默认情况下 authentication-provider 对应一个 DaoAuthenticationProvider,其需要 UserDetailsService(对应 user-service 元素)来获取用户信息 UserDetails(对应 user 元素)。这里我们只是简单的使用 user 元素来定义用户,而实际应用中这些信息通常都是需要从数据库等地方获取的,这个将放到后续再讲。这里我们定义一个用户: 用户名为admin 密码为:123456该用户对应的角色为ROLE_USER(关于这个角色我们需要在授权模块中进行配置)
### 配置授权
我们使用<http>元素来定义web的权限控制
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER"/>
<form-login />
</http>
如上定义中,intercept-url 定义了一个权限控制的规则。pattern 属性表示我们将对哪些 url 进行权限控制,其也可以是一个正则表达式,如上的写法表示我们将对所有的 URL 进行权限控制;access 属性表示在请求对应的 URL 时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,请求的用户只需拥有其中的一个角色就能成功访问对应的 URL。关于access的配置必须要以"ROLE_"开始. use-expressions用于是否使用sqel表达式来解析access,默认取值为true. 如果使用sqel表达式来解析access,那么access的应该写成: hasRole('USER').这样写不是很方便因此我们把use-expressions这个值设置成为了false.
### form-login 元素介绍
http 元素下的 form-login 元素是用来定义表单登录信息的。当我们什么属性都不指定的时候 Spring Security 会为我们生成一个默认的登录页面。如果不想使用默认的登录页面,我们可以指定自己的登录页面。
使用自定义登录页面
自定义登录页面是通过 login-page 属性来指定的。提到 login-page 我们不得不提另外几个属性。
1. username-parameter:表示登录时用户名使用的是哪个参数,默认是 “username”。
2. password-parameter:表示登录时密码使用的是哪个参数,默认是 “password”。
3. login-processing-url:表示登录时提交的地址,默认是 “/login”。这个只是 Spring Security 用来标记登录页面使用的提交地址,真正关于登录这个请求是不需要用户自己处理的。
所以,我们可以通过如下定义使 Spring Security 在需要用户登录时跳转到我们自定义的登录页面。
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.html" login-processing-url="/userLogin" username-parameter="userName" password-parameter="password"/>
</http>
需要注意的是,我们之前配置的是所有的请求都需要 ROLE_USER 权限,这意味着我们自定义的 “/login.html” 也需要登录权限,这样就会形成一个死循环了。解决办法是我们需要给 “/login.html” 放行。我们也可以通过指定一个 http 元素的安全性为 none 来达到相同的效果. 如下
<http pattern="/login.html" security="none"></http>
根据上面的配置,我们自定义的登录页面的内容应该是这样子的:
<form action="/userLogin" method="post">
用户名:<input name="userName"><br>
密码:<input name="password"><br>
<input type="submit" value="登录">
</form>
我们输入用户名和密码以后进行登录,登录以后会出现如下错误:
pring security为了防止CSRF攻击,需要在每个页面中验证成功登录后创建的csrf token值,而我们在静态页面中又无法传递这个token,因此我们可以禁用CSRF这个功能禁用!在<http>标签中加入如下配置:
<csrf disabled="true"/>
在进行登录,我们就可以看到登录成功以后访问的首页信息了.
## http-basic介绍
之前介绍的都是基于 form-login 的表单登录,其实 Spring Security 还支持弹窗进行认证。通过定义 http 元素下的 http-basic 元素可以达到这一效果。
<http use-expressions="false">
<http-basic/>
<intercept-url pattern="/**" access="ROLE_USER" />
<csrf disabled="true"/>
</http>
此时,如果我们访问受 Spring Security 保护的资源时,系统将会弹出一个窗口来要求我们进行登录认证。效果如下:
需要注意的是当我们同时定义了 http-basic 和 form-login 元素时,form-login 将具有更高的优先级。即在需要认证的时候 Spring Security 将引导我们到登录页面,而不是弹出一个窗口。
|
|