avatar

目录
Spring学习 依赖注入

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)。这个实现类作为一个\注册在Spring应用的上下文中,如图所示

QQ截图20191004003626.png

DelegatingFilterProxy 的配置方法如下

  • 在传统的 web.xml 中配置

    QQ截图20191004003641.png

    注意这里将\设置成了 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。该类含有三个方法

  • 用于配置安全策略(包括认证和授权)的方法

    示例如下

    Code
    1
    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
    51
        protected 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);
    

    }

    Code
    1
    2
    3
    该示例中将会提供一个实现 UserDetailService 接口的类,即他会默认实现 UserDetailService 接口里的 LoadByUsername() 方法  

    如果你想自己实现 LoadByUsername() 的方法,可以先使用 @Service 注解来创建实现 UserDetailService 接口的类,然后再将该类传递给 configure() 即可,示例如下

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {

    auth.userDetailsService(userDetailsService);
    

    }

    Code
    1
    2
    3
    4

    * 用于配置 Spring Security 的 Filter 链的方法

    示例如下

    protected void configure(WebSecurity webSecurity) throws Exception {

    Code
    1
    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();

}

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14

而 UserDetailService 则通过 loadByUsername() 的方法来获取 UserDetails 对象

## 认证流程简述
我们以 JWT 为例。

在一个请求被正式处理之前,它首先会经过一系列 Filter (即过滤器),在某一个匹配的 Filter 中,它会执行如下操作

1. 根据传递的信息,构造一个被标识为 “未认证” 的 token
该 token 有多种类型可供选择,例如
* UsernamePasswordAuthenticationToken
* ......

该 UsernamePasswordAuthenticationToken 类的源码如下

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;
    }
}
Code
1
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 设为 “已认证” 并传入 **认证通过** 逻辑,否则传入 **认证失败** 逻辑
文章作者: f1rry
文章链接: http://yoursite.com/2019/08/04/Spring Security/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 F1rry's blog