如果你有幸能看到。
谈一些个人感受
将Spring Security模块添加到应用程序的类路径下。应用程序的类路径下至少包含core和Configuration这两个模块。它经常被用于保护Web应用,添加Web模块,同时还需要JSP标签库。
Spring Security借助一系列Servlet Filter来提供各种安全性功能。
DelegatingFilterProxy是一个特殊的ServletFilter,它本身所作的工作并不多,只是将工作委托给一个Javax.servlet.Filter实现类,这个实现类作为一个
web.xml配置
<filter>
<filter-name>springSecurityFilterChainfilter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterproxyfilter-class>
filter>
DelegatingFilterproxy会将过滤逻辑委托给它。
如果你希望借助于WebApplicationInitializer以JavaConfig配置,需要一个扩展类
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* Created by guo on 2/26/2018.
*/
public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}
AbstractSecurityWebApplicationInitializer
实现了WebapplicationInitializer
,因此Spirng会发现他,并用它在Web容器中注册DelegatingFilterproxy
.它不需要重载任何方法。它会拦截发往应用中的请求,并将其委托给springSecurityFilterChain
Spting Security依赖一系列ServletFilter来提供不同的安全特性。但是你不需要细节。当我们启用Web安全性的时候,会自动创建这些Filter。
Spring 3.2引入了新的java配置方案,完全不需要通过XML来配置安全性功能了。
@Configuration
@EnableWebSecurity //启用Web安全性
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
@EnableWebSecurity启用Web安全功能,但它本身并没有什么用处 ,Spring Security必须配置在一个实现类WebSecurityConfigurer的bean中。
如果你的应用碰巧是在使用Spirng MVC的话,那么就应该考虑使用@EnableWebMvcSecurity
还能配置一个Spring MVC参数解析器。这样的话,处理器方法就能够通过带有@AuthenticationPrincipal注解的参数获取得认证用户的principal。它同时还配置一个bean,在使用Spring表单绑定标签库来定义表单时,这个bean会自动添加一个隐藏的跨站请求伪造(CSRF)token的输入流。
@Configuration
@EnableWebMvcSecurity//启用Web安全性
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
}
这个简单的默认配置指定了如何保护HTTP请求,以及客户端认证用户的方案。通过调用 authorizeRequests()和anyRequest().authenticated()就会要求所欲进入应用的Http都要进行认证,他也配置Spring Securoty支持基于表单的登录以及HTTP Basic方式的认证。
为了让Spring 满足我们应用的需求,还需要在添加一些配置。
除了Spring Security的这些功能,我们可能还希望给予安全限制,有选择性在Web视图上显示特定的内容。
我们所需要的是用户的存储,也就是用户名、密码以及其他信息存储的地方,在进行认证决策的时候,对其进行检索。
好消息是Spring Security非常灵活,能够给予各种数据库存储来认证用户名,它内置了多种常见的用户存储场景,如内存、关系型数据库,以及LDAP,但我们也可以编写并插入自定义的用户存储实现。
借助于Spring Security的Java配置,我们能够很容易的配置一个或多个数据库存储方案。
5、使用基于内存的用户存储
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER","ADMIN");
}
通过简单那的调用inMemoryAuthentication
就能启用内存用户村苏。但是,我们还需要一些用户,否则的话这个没用户并没有且别。需要调用weithuser为其存储添加新的用户。以及给定用户授予一个或多个角色权限的reles()方法
对于调式和开发人员来讲,基于内存的用户存储是很有用的,但对于生产级别应用来讲,这就不是最理想的状态了。
用户数据通常会存储在关系型数据库中,并通过JDBC进行访问。为了配置Spring Security使用以JDBC为支撑的用户存储,我们可以使用jdbcAuthentication()方法,所需的最少配置。
@Autowired
DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource);
}
我们必须要配置的知识一个DataSource,这样的话就能 访问关系型数据库里。
尽管默认的最少配置能够让一切运转起来,但是,它对我们的数据库模式有一些要求。它预期存在某些存储用户数据的表。
public static final String DEF_USERS_BY_USERNAME_QUERY =
"select username,password,enabled " +
"from users " +
"where username = ?";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"select username,authority " +
"from authorities " +
"where username = ?";
public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY =
"select g.id, g.group_name, ga.authority " +
"from groups g, group_members gm, group_authorities ga " +
"where gm.username = ? " +
"and g.id = ga.group_id " +
"and g.id = gm.group_id";
在第一个查询中,我们获取了用户的用户名、密码以及是否启用的信息,这些信息用来进行用户认证。接下来查询查找 了用户所授予的权限,用来进行鉴权。最后一个查询中,查找了用户作为群组的成员所授予的权限。
如果你能够在数据库中定义和填充满足这些查询的表,那么基本上就不需要你在做什么额外的事情了。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(
"select username,password,true" +
"from Spitter where username=?")
.authoritiesByUsernameQuery(
"select username,'ROLE_USER' from Spitter where username=?");
}
在本例中,我们只重写了认证和基本权限的查询语句,但是通过调用groupAuthoritiesByUsername()
方法,我们也能够将群组权限重写为自定义的查询语句。
为了解决密码明文的问题,我们借助于passwordEncode()方法指定一个密码转码器(encoder)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
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("53cd3t"));
}
passwordEncoder()方法可以接受Spring Security中passwordEncoder接口的任意实现。加密模块包含了三个这样的实现
上述代码使用了StandardPasswordEncoder
,但是如果内置的实现无法满足需求时,你可以提供自定义的实现 ,passwordEncoder
接口如下:
package org.springframework.security.crypto.password;
/**
* Service interface for encoding passwords.
*/
public interface PasswordEncoder {
/**
* Encode the raw password.
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw password after it too is encoded.
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
}
不管使用哪一个密码转化器,都需要理解的一点是:数据库的秘密是永远不会解码的,所采取的策略与之相反。用户在登录时输入的密码会按照相同的算法进行转码,然后在于数据库中已经转码过的密码进行对比,这个对比是在PasswordEncoder
的matches()方法中进行的。
为了让Spring Security使用基于LDAP的认证,我们可以使用ldapAuthentication()方法,这个方法类似于jdbcAuthentication()
只不过是LDAP版本
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchBase("(uid={0})")
.groupSearchFilter("member={0}");
}
配置密码比对
基于LDAP进行认证的默认策略是进行绑定操作,直接通过LDAP服务器认证用户,另一种可选的方式是进行对比,涉及到输入的 密码发送到LDAP目录上,并要求服务器将这个密码和用户的密码进行对比,因为对比是用LDAP服务器内完成的。实际的秘密能保持私密。
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.groupSearchBase("on=people")
.userSearchBase("(uid={0})")
.groupSearchFilter("member={0}")
.groupSearchBase("on=groups")
.groupSearchFilter("member={0}")
.passwordCompare();
}
如果密码被保存在不同的属性中,可以通过passwordAttribute()
方法来声明密码属性的名称
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.groupSearchBase("on=people")
.userSearchBase("(uid={0})")
.groupSearchFilter("member={0}")
.groupSearchBase("on=groups")
.groupSearchFilter("member={0}")
.passwordCompare()
.passwordEncoder(new Md5PasswordEncoder())
.passwordAttribute("passcode");
}
为了避免这一点我们可以通过调用passwordEncoder()
方法指定加密策略。本例中使用MD5加密,这需要LDAP服务器上密码也是MD5进行加密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.groupSearchBase("on=people")
.userSearchBase("(uid={0})")
.groupSearchFilter("member={0}")
.groupSearchBase("on=groups")
.groupSearchFilter("member={0}")
.contextSource()
.root("dc=guo,dc=com"); //.url()
.ldif("classpath:users.ldif"); //这里是可以分开放的,需要定义users.ldif文件
}
在任何的应用中,并不是所有的页面都需要同等程度地保护。尽管用户基本信息页面时公开的。但是,如果当处理“/spitter/me”时,通过展现当前用户的基本信息那么就需要进行认证,从而确定要展现谁的信息。
对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)
方法。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("spitters/me").authenticated() //进行认证。
.antMatchers(HttpMethod.POST,"/spittles").authenticated() //必须经过认证
.anyRequest().permitAll(); //其他所有请求都是允许的,不需要认证。
}
antMatchers()
方法中设置的路径支持Ant风格的通配符。
.antMatchers("spitters/**").authenticated()
.antMatchers("spitters/**","spittles/mine").authenticated()
.antMatchers("spitters/.*").authenticated()
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("spitters/me").authenticated()
.antMatchers(HttpMethod.POST, "/spittles")
.hasAuthority("ROLE_SPITTER")
.anyRequest().permitAll();
}
要求用户不仅需要认证,还要具备ROLE_SPITTER权限。作为替代方案,还可以使用hasRole()方法,它会自动使用“ROLE_”前缀
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("spitters/me").authenticated()
.antMatchers(HttpMethod.POST,"/spittles").hasRole("SPITTER")
.anyRequest().permitAll();
}
很重要的一点是将最为具体的请求路径放到最前面,而最不具体的路径放到最后面,如果不这样做的话,那不具体的配置路径将会覆盖掉更为具体的路径配置
比SpEL更为强大的原因在于,HasRole()仅仅是Spring支持的安全相关表达式中的一种。
Spring Security支持的所有表达式。
在掌握了Spring Security 的SpEL表达式后,我们就能够不再局限于基于用户的权限进访问限制了。
使用HTTP提交数据是一件具有风险的事情。通过HTTP发送的数据没有经过加密,黑客就有机会拦截请求并且能够看到他们想看到的信息。这就是为什么铭感的 数据要通过HTTPS来加码发送的原因。
使用HTTPS似乎很简单,你要做的事情只是在URL中的HTTP后加上一个字母“s”就可以了,是吗? 是的,不加也可以的。哈哈哈。。。
这是真的,但这是把使用的HTTPS通道的责任放在了错误的地方。
为了保证注册表单的数据通过HTTPS传递,我们可以在配置中添加requiresChannel()
方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("spitters/me").authenticated()
.antMatchers(HttpMethod.POST,"/spittles").hasRole("SPITTER")
.anyRequest().permitAll()
.and()
.requiresChannel()
.antMatchers("/spitter/form").requiresSecure(); //需要HTTPS
}
不论何时,只要是对“/spitter/form”的请求,Spring Security 都视为需要安全通道(通过调用requiresChannel()确定
)并自动将请求重定向到HTTPS上。
与之相反,有些页面并不需要设置通过HTTPS传递。将首页声明为始终通过HTTP传送。
.antMatchers("/").requiresInecure();
如果通过HTTPS发送了对"/"的请求,Spring Security将会把请求重定向到不安全的HTTP通道上。
如果POST的请求来源于其他站点的话,跨站请求伪造(cross-site request forgery CSRF),简单来讲,如果一个站点欺骗用户提交请求到其他服务器上的话,就会发生CSRF攻击,这可能会带来消极的后果。从Spring Security 3.2开始,默认就会启用CSRF防护。实际上,除非你 采取行为处理CSRF防护或者将这个功能禁用。否则的话,在应用提交表单的时候会遇到问题。
Spring Security 通过一个同步的token的方式来实现CSRF防护的功能。它将会拦截状态变化的请求并检查CSRF token,如果请求中不包含 CSRF token的话,或者token不能与服务器端的token相匹配,请求将会失败,并抛出CsrfException异常。
这意味着在你的应用中,所有的表单必须在一个"_csrf"域中提交token,而且这个token必须要与服务器端计算并存储的token一致。这样的话当表单提交的时候,才能匹配。
好消息是Spirng Security已经简化了将token放到请求属性中这一任务。如果你使用Thymeleaf作为页面模板的话,只要