Spring Security是为基于spring的应用程序提供声明式安全保护的安全性框架。它能够在Web请求级别和方法调用级别处理身份认证和授权。
Spring Security被分成11个模块
模块 | 描述 |
---|---|
ACL | 支持通过访问控制列表(access control list,ACL)为域对象提供安全性 |
切面(Aspects) | 一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOP |
CAS客户端(CAS Client) | 提供Jasig的中心认证服务(Central Authentication Service, CAS)进行集成的功能 |
配置(Configuration) | 包含通过XML和Java配置Spring Security的功能支持 |
核心(core) | 提供Spring Security基本库 |
加密(Cryptography) | 提供加密和密码编码的功能 |
LDAP | 支持基于LDAP进行认证 |
OpenID | 支持使用OpenID进行集中式认证 |
Remoting | 提供了对Spring Remoting的支持 |
标签库(Tag Library) | Spring Security的JSP标签库 |
Web | 提供了Spring Security基于Filter的Web安全性支持 |
应用程序的类路径下至少要包含Core和Configuration这两个模块。
使用Spring Security,只需要在Servlet上下文配置一个Filter——DelegatingFilterProxy就可以,这是一个特殊的Servlet Filter,它本身所做的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个
注册在Spring应用上下文中。下面是两种配置方式:
XML:在web.xml中
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
filter>
Java配置:创建一个扩展的新类
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer{
}
Java配置:
任何实现了WebSecurityConfigure的类都可以用来配置Spring Security。更简单的方式是扩展WebSecurityConfigure的一个实现类WebSecurityConfigurerAdapter。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{}
如果你的应用是使用Spring MVC开发的,可以使用@EnableWebMvcSecurity来替换:
@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{}
具体的配置可以通过重写WebSecurityConfigurerAdapter的configure()方法来实现,下面是三个方法的功能:
方法 | 描述 |
---|---|
configure(HttpSecurity) | 通过重载,配置如何通过拦截器保护请求 |
configure(WebSecurity) | 通过重载,配置Spring Security的Filter链 |
configure(AuthenticationManagerBuilder) | 通过重载,配置user-detail服务 |
下面是几个示例:
1.配置用户存储,这里使用基于内存的用户存储,此外,它还支持基于数据库表认证、基于LDAP进行认证以及自定义的用户服务(提供一个自定义的UserDetailsService接口实现。)
/**
* 配置用户存储
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//使用内存配置用户存储
auth.inMemoryAuthentication()
.withUser("xuexiaoqiang").password("123").roles("USER").and()
.withUser("admin").password("admin").roles("USER","ADMIN");//添加了两个用户并配置了用户角色权限
//使用以JDBC为支撑的用户存储
// auth.jdbcAuthentication()
// .dataSource(dataSource)
// .usersByUsernameQuery(
// "select username, password, true " +
// "from Spitter where username=?")//重写查询语句
// .authoritiesByUsernameQuery(
// "select username, 'ROLE_USER' from Spitter where username=?")
// .passwordEncoder(new StandardPasswordEncoder("53cr3t"));//指定密码转换器
}
2.指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限:
/**
* 请求控制
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/me").authenticated() //对路径“/me”的请求需要进行认证
.antMatchers(HttpMethod.GET, "/index").hasAuthority("ROLE_ADMIN") //对路径“/index”下的get请求需要ADMIN权限
.anyRequest().permitAll()//其他的所有请求不需要认证
.and()
.requiresChannel()
.regexMatchers("/form").requiresSecure()//只要是对"/form"的请求,Spring Security都视为需要安全通道并自动将请求重定向到HTTPS上。
.antMatchers("/").requiresInsecure();//对“/”的访问用HTTP通道
//http.csrf().disable();//禁用CSRF防护功能
//启用默认的登录页
http.formLogin();
//启用remember me功能
http.rememberMe()
.tokenValiditySeconds(2419200)//指定token生命周期
.key("testKey");//私钥
//退出后重定向到其他页面,默认是返回登录页
http.logout()
.logoutSuccessUrl("/")
.logoutUrl("/signout");//重写LogoutFilter拦截路径,默认为/logout
}
//这里的ant指的是ant风格的通配符,regexMatchers()则是正则风格的通配符
除了authenticated() 和permitAll(),AuthorizedUrl还有下面的方法用来定义如何保护路径的配置方法
方法 | 描述 |
---|---|
not() | 对其他访问方法的结果求反 |
hasRole(String role) | 如果用户具备给定角色的话,就允许访问 |
hasAnyRole(String… roles) | 如果用户具备给定角色中的某一个的话,就允许访问 |
hasAuthority(String authority) | 如果用户具备给定权限的话,就允许访问 |
hasAnyAuthority(String… authorities) | 如果用户具备给定权限中的某一个的话,就允许访问 |
hasIpAddress(String ipaddressExpression) | 如果请求来自给定IP的话,就允许访问 |
permitAll() | 无条件允许访问 |
anonymous() | 允许匿名用户访问 |
rememberMe() | 如果用户是通过Remember-me功能认证的,就允许访问 |
denyAll() | 无条件拒绝所有访问 |
authenticated() | 允许认证过的用户访问 |
fullyAuthenticated() | 如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问 |
access(String attribute) | 如果给定的spEl表达式计算结果为true,就允许访问 |
配置的规则是将最为具体的请求路径放在前面,而最不具体的路径(如antRequest())放在最后面。否则的话,不具体的路径配置将会覆盖掉更为具体的路径配置。
上面提到了access方法,它可以使用spEL表达式进行访问控制
Spring Security通过一些安全性相关的表达式扩展了Spring表达式语言
安全表达式 | 计算结果 |
---|---|
authentication | 用户的认证对象 |
denyAll | 结果始终未false |
hasAnyRole(list of roles) | 如果用户被授予了列表中任意的指定角色,结果为true |
hasRole(role) | 如果用户被授予了指定的角色,结果为true |
hasIPAddress(IP Address) | 如果请求来自指定IP的话,结果为true |
isAnonymous() | 如果当前用户为匿名用户,结果为true |
isAuthenticated() | 如果当前用户进行了认证的话,结果为true |
isFullyAuthenticated() | 如果当前用户进行了完整认证的话(不是通过Remember-me功能进行的认证),结果为true |
isRememberMe() | 如果当前用户是通过Remember-me自动认证的,结果为true |
permitAll | 结果始终为true |
principal | 用户的principal对象 |
spELl的特点就是可以对各种控制进行自由的搭配,比如下面这个
http.authorizeRequests()
.regexMatchers("/*")
.access("hasRole('ROLE_ADMIN') and hasIpAddress('192.168.1.2')");
强制通道:
使用requiresChannel()方法能够为各种URL模式声明所要求的通道。
防止跨站请求伪造——CSRF:
Spring Security通过一个同步token的方式来实现CSRF防护的功能。
这意味着在应用中所有的表单必须在一个“_csrf”域中提交token,而且这个token必须要与服务器端计算并存储的token一致,这样的话当表单提交的时候,才能进行匹配。
下面是使用不同的模板时使用csrf的方式:
Thymeleaf:在action属性添加Thymeleaf命名空间前缀,下面的th:,就会自动生成一个“_csrf”隐藏域。
<form method="POST" th:action="@{/websecurity}">
JSP:
type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
如果使用Spring的表单绑定标签的话
标签会自动为我们添加隐藏的CSRF token标签。
下面是Spring Security默认的登录页面,里面就有“_csrf”隐藏域以及token。
<html>
<head>
<title>Login Pagetitle>
head>
<body onload='document.f.username.focus();'>
<h3>Login with Username and Passwordh3>
<form name='f' action='/websecurity/login' method='POST'>
<table>
<tr>
<td>User:td>
<td><input type='text' name='username' value=''>td>
tr>
<tr>
<td>Password:td>
<td><input type='password' name='password' />td>
tr>
<tr>
<td><input type='checkbox' name='remember-me' />td>
<td>Remember me on this computer.td>
tr>
<tr>
<td colspan='2'><input name="submit" type="submit"
value="Login" />td>
tr>
<input name="_csrf" type="hidden"
value="bdd0204e-c2d4-45b9-a11c-6e6b080d243c" />
table>
form>
body>
html>
Remember-me:
默认情况下,这个功能是通过在cookie中存储一个token完成的,这个token最多两周有效。
存储在cookie中的token包含用户名,密码,过期时间,和一个私钥——在写入cookie前都进行了MD5哈希。
启用了Remember-me功能后,我们只需要在登录表单中增加这样一个复选框就可以了,不用写value值,如果被选中的话,会发送一个“on”过去:
type="checkbox" name="remember-me" id="remember-me" />
Spring Security本身提供了一个JSP标签库,而Thymeleaf通过特定的方言实现了与Spring Security的集成。
声明:
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security"%>
使用这个标签库要记得加依赖:
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-taglibsartifactId>
<version>4.2.2.RELEASEversion>
dependency>
JSP标签 | 作用 |
---|---|
|
如果用户通过访问控制列表授予了指定的权限,那么渲染该标签体重的内容 |
|
渲染当前用户认证对象的详细信息 |
|
如果用户被授予了特定的权限或者SpEL表达式的计算结果为true,那么渲染该标签体中的内容 |
例如:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security"%>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title heretitle>
head>
<body>
<h1>My Name is <security:authentication property="principal.username"/>h1>
<security:authentication property="principal.password" var="password" scope="request"/>
<security:authorize access="hasRole('ROLE_ADMIN')">
<h1>哈哈哈,这里只有具有ADMIN权限的用户才能看到h1>
security:authorize>
body>
html>
使用
JSP标签来访问用户的认证详情
认证属性 | 描述 |
---|---|
authorities | 一组用于表示用户所授予权限的GrantedAuthority对象 |
Credentials | 用于核实用户的凭证(通常,这会是用户的密码) |
details | 认证的附加信息(IP地址,证件序列号、会话ID等) |
principal | 用户的基本信息对象 |
Thymeleaf的安全方言提供了与Spring Security标签库相对应的属性:
属性 | 作用 |
---|---|
security:authentication | 渲染认证对象的属性。类似于Spring Security 的
|
security:authorize | 基于表达式的计算结果,条件性的渲染内容。类似于Spring Security的
|
security:authorize-acl | 基于表达式的计算结果,条件下的渲染内容。类似于
|
security:authorize-expr | security:auhorize属性的别名 |
security:authorize-url | 基于给定URL路径相关的安全规则,条件性的渲染内容。类似于 标签使用url属性时的场景。 |
为了使用安全方言,需要确保Thymeleaf Extras Spring Security已经位于应用的类路径下。然后,还需要在配置中使用SpringTemplateEngine来注册SpringSecurityDialect:
"templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
...
"additionalDialects">
"org.thymeleaf.extras.springsecurity4.dialect.SpringSecurityDialect"/>
...
然后再模板中声明命名空间:
"http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:security="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
...
然后就可以使用了:
<div th:text="${#authentication.name}">
The value of the "name" property of the authentication object should appear here.
div>
<div th:if="${#authorization.expression('hasRole(''ROLE_ADMIN'')')}">
This will only be displayed if authenticated user has role ROLE_ADMIN.
div>