Spring Security简介
SpringSecurity是为基于Spring的应用程序提供声明式安全保护的安全性框架。Spring Security提供了完整的安全性解决方案,能够在Web请求级别和方法调用级别处理身份认证和授权。因为基于Spring,所以充分利用了依赖注入和面向切面技术。
SpringSecurity3.2被分为11个模块
模块 | 描述 |
ACL | 支持通过访问控制列表为域对象提供安全性 |
切面 | 一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ切面,而不是使用标准的Spring AOP |
CAS客户端 | 提供与Jasig的中心认证服务进行集成的功能 |
配置 | 包含通过XML和java配置Spring Security的功能支持 |
核心 | 提供Spring Security基本库 |
加密 | 提供了加密和密码编码的功能 |
LDAP | 支持基于LDAP进行认证 |
OpenID | 支持使用OpenID进行集中式认证 |
Remoting | 提供了对Spring Remoting的支持 |
标签库 | Spring Security的JSP标签库 |
Web | 提供了Spring Security基于Filter的Web安全性支持 |
应用程序的类路径下至上要包含Core和Configuration两个模块。
过滤Web请求
SpringSecurity借助一系列Servlet Filter来提供各种安全性功能。DelegatingFilterProxy是一个特殊的Servlet Filter,它本身工作不多,只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个bean注册在Spring应用上下文中。
传统web.xml配置
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
Java配置:
public class SecurityInit extends AbstractSecurityWebApplicationInitializer {}
AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer,因此Spring会发现它,并用它在Web容器中注册DelegatingFilterProxy。
两种配置方式都会拦截发往应用中的请求,并将请求委托给ID为springSecurityFilterChain的bean。
springSecurityFilterChain本身是另一个特殊的Filter,也被称为FilterChainProxy。可以连接任意数量的其他的Filter。
在Spring Security的早期,为了启用安全功能需要上百行的XML配置。Spring Security2.0提供了安全性相关的XML配置命名空间,Spring3.2引入了新的Java配置方案。如下是最简单的java配置
@Configuration
@EnableWebSecurity //@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
@EnableWebSecurity注解将会启用Web安全功能。但它本身没有什么用处,Spring Security必须配置在一个实现了WebSecurityConfigurer的bean中或者扩展WebSecurityConfigurerAdapter。如果应用是Spring MVC开发的,可以考虑使用 @EnableWebMvcSecurity注解,区别在于@EnableWebMvcSecurity注解的还配置了一个SpringMVC参数解析的解析器,这样的话处理器方法就能够通过带有@AuthenticationPrincipal注解的参数获得认证用户的principal;同时还配置了一个bean,在使用Spring表单绑定标签库来定义表单时,这个bean会自动添加隐藏的跨站请求伪造token输入域。
为了指定Web安全的细节,可以通过重载WebSecurityConfigurerAdapter中的一个或多个方法来实现。
方法 | 描述 |
configure(WebSecurity) | 通过重载,配置Spring Security的Filter链 |
configure(HttpSecurity) | 通过重载,配置如何通过拦截器保护请求 |
configure(AuthenticationManagerBuilder) | 通过重载,配置user-detail服务 |
基于上表可以得出上边的java配置Security的例子,将应用锁定了。等同于如下:
@Configuration
@EnableWebSecurity //@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
}
}
通过调用authorizeRequests()和anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证。同时由于没有重载configure(AuthenticationManagerBuilder)方法,所以没有用户存储支持认证等于没有用户,所以就是所有的请求都要求认证,但是没人能登录。
选择查询用户详细信息的服务
SpringSecurity非常灵活,能够基于各种数据存储来认证用户。它内置了多种常见的用户存储场景,如内存、关系型数据库以及LDAP,而且可以程序员编写自定义的用户存储实现。
基于内存的用户存储
由于安全配置类扩展了WebSecurityConfigurerAdapter,因此配置用户存储最简单的方式就是重载configure()方法,并以AuthenticationManagerBuilder作为入参。AuthenticationManagerBuilder有多个方法可以用来配置SpringSecurity对认证的支持,通过inMemoryAuthentication()方法,可以启用、配置并任意填充基于内存的用户存储。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
.withUser("admin").password("123456").roles("USER").roles("USER","ADMIN");
//下边与上边等价
auth.inMemoryAuthentication().withUser("user").authorities("ROLE_USER").and()
.withUser("admin").password("123456").authorities("ROLE_USER","ROLE_ADMIN");
}
配置用户详细信息的方法如下:
方法 | 描述 |
accountExpired(boolean) | 定义账号是否已过期 |
accountLocked(boolean) | 定义账号是否被锁定 |
and() | 用于连接配置 |
authorities(grantedAuthority…) | 授予某个用户一项或多项权限 |
authorites(List<? Extends grantedAutority> | 授予某个用户一项或多项权限 |
authorities() | 授予某个用户一项或多项权限 |
credentialsExpired(boolean) | 定义凭证是否过期 |
disabled(boolean) | 定义账号是否被禁用 |
password(String) | 定义用户密码 |
roles(String…) | 授予某个用户一项或多项角色 |
基于数据库表进行认证
以下是基于jdbc为支持的用户存储的最少配置
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
常用配置方式:
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("select username.password,true" +
"from USER where username = ?").authoritiesByUsernameQuery("select username,'ROLE_USER' from USER " +
"where username = ?");
}
上述例子只重写了认证和基本权限的查询语句,但是通过调用groupAuthoritiesByUsername()方法,可以将群组权限重写为自定义的查询语句。
将默认的SQL查询替换为自定义的设计时,很重要的一点就是要遵循查询的基本协议。所有查询都将用户名作为唯一的参数。认证查询会选取用户名、密码以及启用状态信息。
使用转码后的密码
一般数据库中存放的密码均为密文,为了解决密文的问题,需要借助passwordEncoder()方法指定一个密码转换器。
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource).usersByUsernameQuery("select username.password,true" +
"from USER where username = ?").authoritiesByUsernameQuery("select username,'ROLE_USER' from USER " +
"where username = ?").passwordEncoder(new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return null;
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return false;
}
});
}
passwordEncoder()方法可以接受SpringSecurity中PasswordEncoder接口的任意实现。Spring提供了一些默认实现,如:StandardPasswordEncoder、BCryptPasswordEncoder、AbstractPasswordEncoder等等。
基于LDAP进行认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(uid={0})").groupSearchFilter("member={0}");
}
方法userSearchFilter()和groupSearchFilter()用来为基础LDAP查询提供过滤条件,分别用于搜索用户和组。默认情况下是空的也就是从LDAP层级的根开始。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从people的组织单元下搜索而不是从根开始,组应该在名为groups的组下搜索
auth.ldapAuthentication().userSearchBase("ou=people").userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups").groupSearchFilter("member={0}");
}
增加密码校验,passwordEncoder()方法指定加密策略
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 增加密码校验
auth.ldapAuthentication().userSearchBase("ou=people").userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups").groupSearchFilter("member={0}")
.passwordCompare().passwordEncoder(new Md4PasswordEncoder()).passwordAttribute("password");
}
默认情况下,SpringSecurity的LDAP认证假设LDAP服务器监听本地的33389端口。可以通过contextSource()方法来配置地址。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 配置远程的LDAP服务器
auth.ldapAuthentication().userSearchBase("ou=people").userSearchFilter("(uid={0})")
.groupSearchBase("ou=groups").groupSearchFilter("member={0}")
.contextSource().url("ldap://asdf.com:1234/dc=test,dc=com");
}
配置自定义用户对象
有时需要认证的用户存储在非关系型数据库,或者已有的不能支持需求。Spring Security提供了一个自定义的UserDetailsService接口,只需实现该接口就可实现自定义的用户对象。
拦截请求
重载configure(HttpSecurity)方法可以对每个请求进行细粒度的安全性控制。例如:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.antMatchers("/test/aaa").authenticated()
.antMatchers(HttpMethod.POST,"/getInfo").authenticated()
.anyRequest().permitAll();
}
定义保护路径的配置方法如下:
方法 | 描述 |
access(String) | 如果给定的SpEL表达式计算结果为true,就允许访问 |
anonymous() | 允许匿名用户访问 |
authenticated() | 允许认证过的用户访问 |
denyAll() | 无条件拒绝所有访问 |
fullyAuthenticated() | 如果用户是完整认证的话(不是通过remember-me功能认证),就允许访问 |
hasAnyAuthority(String…) | 如果用户具备给定权限中的某一个的话,就允许访问 |
hasAnyRole(String..) | 如果用户具备给定角色中的某一个的话,就允许访问 |
hasAuthority(String) | 如果用户具备给定权限的话,就允许访问 |
hasIpAddress(String) | 如果请求来自给定IP地址的话,允许访问 |
hasRole(String) | 如果用户具备给定角色的话,允许访问 |
not() | 对其他访问方法的结果求反 |
permitAll() | 无条件允许访问 |
rememberMe() | 如果童虎是通过remember-me功能认证的,允许访问 |
我们可以将以上任意数量的方法连接器满足安全需要,但是,这些规则会按照给定的顺序发挥作用,所以需要将最具体的请求路径放在前面,最不具体的放在最后面。如果不这样,不具体的路径配置会覆盖掉更具体的路径配置。
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//.requiresChannel()指定是否需要安全通道并自动将请求重定向到HTTPS(HTTP)上
httpSecurity.authorizeRequests()
.antMatchers("/test/aaa").authenticated()
.and().requiresChannel().antMatchers("111").requiresSecure();
}
认证用户
认证用户相关方法
@Override
protected void configure(HttpSecurityhttpSecurity) throws Exception {
//跳转登录页,并指定登录页
httpSecurity.formLogin().loginPage("/login").and()
//启用HTTPBasic认证。
.httpBasic().realmName("test").and()
//启用rememberme 指定失效时间 指定key 在前端页面增加remember me参数
.rememberMe().tokenValiditySeconds(123123).key("key").and()
//启用退出功能 指定退出页面
.logout().logoutSuccessUrl("/").logoutUrl("/signOut").and()
.authorizeRequests().antMatchers("......");
}
保护视图
SpringSecurity提供了一个JSP标签库,Thymeleaf通过特定的方言实现了Spring Security。
以下为SpringSecurity通过JSP标签库在视图层上支持安全性
JSP标签 | 作用 |
<security:accesscontrollist> | 如果用户通过访问控制列表授予了指定的权限,那么渲染改标签体重的内容 |
<security:authentication> | 渲染当前用户认证对象的详细信息 |
<security:authorize> | 如果用户被授予了特定的权限或者SpEL表达式计算结果为true,那么渲染改标签体中的内容 |
以下为Thymeleaf的SpringSecurity方言
属性 | 作用 |
sec:authentication | 渲染认证对象的属性。类似于Spring Security的<sec:authentication/>JSP标签 |
sec:authorize | 基于表达式的计算结果,条件性的渲染内容。类似于Spring Security的<sec:authorize/>标签 |
sec:authorize-acl | 基于表达式的计算结果,条件性的渲染内容。类似于Spring Security的<sec:accesscontrollist/>JSP标签 |
sec:authorize-expr | sec:authorize属性的别名 |
sec:authorize-url | 基于给定URL路径相关的安全规则,条件性的渲染内容。类似于Spring Security的<sec:authorize/>JSP标签使用url属性时的场景 |





