Spring Security
Spring Security简介
Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架。 Spring Security 提供了完整的安全性解决方案,它能够在Web请求级别和方法调用级别身份认证和授权。
因为基于Spring框架,所以Spring Security 充分利用了 依赖注入 和 面向切面 的技术。
应用程序的安全性通常体现在两方面
认证
授权
而Spring Security 也绕不开这两个方面
认证
当认证请求到达时,Spring Security 会接受请求中的认证信息(如用户名和密码),然后通过 UserDetailService 类中的方法 用传递过来的用户名 来获取 UserDetail 对象(即用户信息),然后检验 UserDetails 对象中的密码是否与 传递过来的密码 相匹配,如果匹配,则认证成功,执行相应操作(如返回 token)否则,认证失败。授权
当用户认证成功时,Spring Security 凭借 UserDetails 对象中的权限信息来判断用户的某个操作是否被授权
过滤Web请求(DelegatingFilterProxy 与 SpringSecurityFilterChain)
借助于Spring的小技巧,我们只需配置一共Filter就可以提供各种安全性功能了。
DelegatingFilterProxy 是一个特殊的 Servlet Filter,它本身所做的工作并不多。只是将一个工作委托给一个 javax.servlet.Filter 实现类(在 Spring Security 中,该实现类就是 springSecurityFilterChain)。这个实现类作为一个\
DelegatingFilterProxy 的配置方法如下
在传统的 web.xml 中配置
注意这里将\
设置成了 springSecurityFilterChain
当我们配置 Spring Security 到 Web 安全性中, 这里会有一个名为 SpringSecurityFilterChain 的 Filter bean ,DelegatingFilterProxy 会将过滤逻辑委托给它以Java的方式来配置
package spitter.config; import org,springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {}
AbstractSecurityWebApplicationInitializer 实现了 WebApplicationInitializer,因此Spring会发现他,并用它在Web容器中注册DelegatingFilterProxy 。
不管是用哪种方式来配置 DelegatingFilterProxy ,它都会拦截发送应用中的请求,并将请求委托给ID 为 springSecurityFilterChain bean .
springSecurityFilterChain 本身是另一个特殊的 Filter,它也被成为 FilterChainProxy. 它可以链接任意一个或多个其他的 Filter. Spring Security 依赖一系列 Servlet Filter 来提供不同的安全特性。
你不需要显示声明 springSecurityFilterChain 以及它所链接在一起的其他Filter。当我们启用 Web 安全性的时候,会自动创建这些Filter。
WebSecurityConfigureAdapter类
一个继承 WebSecurityConfigureAdapter类 的子类将被用于配置 Spring Security。该类含有三个方法
用于配置安全策略(包括认证和授权)的方法
示例如下
Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antiMatchers("/admin/api/**").hasRole("ADMIN")
.antiMatchers("/app/api/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.httpBasic()
}
```
HttpSecurity 对象会提供多种方法来返回用于各种配置的对象,例如
* authorizeRequests()
它会返回一个 URL 拦截注册器,我们可以使用它的多种方法来匹配系统的 URL,为其指定安全策略
* anyRequest()
* antMatchers()
* regexMatchers()
* ...
同时还提供一个 and() 方法来让我们退出 URL拦截注册器的配置,而返回到 HttpSecurity 对象,以便继续其他配置,下面的方法返回的对象中都包含该 and() 方法。
* formLogin()
它会返回一个用于配置表单认证的对象,该对象也包含一些方法用于配置表单认证
* loginPage()
用于配置登录页面的地址
* loginProcessingUrl()
用于指定处理登录请求的url
* successHandler()
指定登录成功以后的处理逻辑,该方法需要传入一个继承 AuthenticationSuccessHandler 的类
* failureHandler()
指定登录失败以后的处理逻辑,该方法需要传入一个继承 AuthenticationFailureHandler 的类
* ...
* httpBasic()
它会返回一个用于配置 HTTP 基本认证的对象
* csrf()
它会返回一个用于配置 csrf 策略的对象
* 用于设置实现 UserDetailService 接口的类 的方法
示例如下protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.jdbcAuthentication() .dataSource(dataSource);
}
Code1
2
3该示例中将会提供一个实现 UserDetailService 接口的类,即他会默认实现 UserDetailService 接口里的 LoadByUsername() 方法
如果你想自己实现 LoadByUsername() 的方法,可以先使用 @Service 注解来创建实现 UserDetailService 接口的类,然后再将该类传递给 configure() 即可,示例如下@Autowired
private UserDetailsService userDetailsService;@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService);
}
Code1
2
3
4
* 用于配置 Spring Security 的 Filter 链的方法
示例如下protected void configure(WebSecurity webSecurity) throws Exception {
Code1
2
3
4
5一般不需要管他
## UserDetails 与 UserDetailService
在 Spring 中 一个实现了 UserDetail 接口的 Entity 实体,我们称之为 UserDetail 对象。
UserDetails 接口如下
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
1 |
|
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 510L;
private final Object principal;
private Object credentials;
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
Code1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
其中
* principal 表示用户标识(例如用户名)
* credentials 表示用户的认证凭据(例如密码)
* this.setAuthenticated() 用于改变 authenticated 字段(该字段用来表示该 token 是否已经被 “认证”)
2. 根据 token 的类型来选择恰当的 **AuthenticationProvider** 类来检验该 token 中的信息是否正确
Filter 会通过调用
this.getAuthenticationManager().authenticate(token);
来自动选择相应的 **AuthenticationProvider** 类的 authenticate() 方法来检验该 token
对应于上面的 UsernamePasswordAuthenticationToken 其对应的 Provider 就是 **DaoAuthenticationProvider**
至于如何自动选择,则是先通过如下方法获得一个 AuthenticationManager 对象
this.getAuthenticationManager()
该对象中注册了很多 AuthenticationProvider,但每个 AuthenticationProvider 都实现了 **AuthenticationProvider 接口**,该接口源码如下
public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
```
其中第二个方法就是用于做自动选择的。
**AuthenticationManager 对象** 在调用 authenticate() 时,会先遍历已注册的 **DaoAuthenticationProvider** ,并在每趟遍历中会将 token的 **类对象** 传递给他们的 supports 方法,如果返回为 true ,则表示该 Provider 可以处理该token ,于是就会调用该 Provider 的 authenticate() 方法来检验 **由第一步所构造出的 token**
而在调用 authenticate() 方法时就会用到 **UserDetailsService** 中的 loadUserByUsername() 方法 来获取系统中的 UserDetails 对象,来与 token 中的数据进行比对,如果比对成功,就将该 token 设为 “已认证” 并传入 **认证通过** 逻辑,否则传入 **认证失败** 逻辑