Spring Security浅析

1 作用

从名称就可看出,它是用于web安全的,比如对于一些非法用户,非法请求,状态管理等,我们都需要一定的机制去进行相应的处理。
从一个最简单的场景出发,思考一下我们该如何实现这种安全机制。比如对于不同的用户而言,能够请求的资源也应该不同。
从最原始的想法考虑,在需要权限控制的controller里面,比如说查看后台统计信息,我们首先想到的是直接在这个controller里面判断当前用户是否具有足够的权限。
虽然这种想法很直接,但是如果系统相当大,这样的controller会很多,无疑增加了controller的耦合程度,因为对于后台统计信息的controller而言,他并不需要做用户权限检查,他只需要返回统计信息即可。我们再仔细想一想,是不是应该将这种安全验证方面的模块从controller里面分离出来,而这正是AOP的思想。对于像这种安全验证的非业务性的公共服务模块,可能在很多业务模块中使用,最好的做法是统一管理,即写成一个AOP切面。

2 实现原理

从上面我们知道了Spring Security的背景,当然我们可以自己通过AOP方式实现这种安全验证模块,但是Spring Security 根据AOP和代理等思想已经实现了这样一个框架。我们只需要在其基础上完成我们的功能即可。
开始看Spring Security源码的时候,我们很可能会被它庞大的框架绕晕。但是从宏观上看,Spring Security就是根据Spring Aop 和 Servlet 的Filter规范来实现的。
那么我们要考虑的问题就是:

2.1 模块需求

需要的模块:1.Filter模块(和Servlet有关)>>>>>2.认证授权(Authenticate/Authorize)模块(FIlter的具体逻辑实现)>>>3.实体数据(userdetailes)模块(认证授权操作的数据实体对象)

2.2 模块管理

如何管理这些模块:既然用到了Spring,自然所有的这些模块都应交由Spring来管理,比如何时生成Filter,何时进行认证授权等。但是这里就有一个极大的问题,Filter是在Servlet环境中的,所有的请求都是先经过Servlet的Filter的,和Spring并没有直接关系。所以如果我们要实现Spring管理Filter,则需要两点:①.在Spring容器中创建Filter ②.所有的请求最终都要经过Spring中的Filter。于是Spring Security就引出了第一个配置项:DelegationFilterProxy或者说springSecurityFilterChain

```
//配置方案:
	<filter>
	    <!--filter-name是固定的,不能更改-->
	    <filter-name>springSecurityFilterChain</filter-name>
	    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
```
> DelegationFilterProxy
> 相当于是Spring中的Filter暴露给Servlet的Filter的统一代理接口,其具体实现是委托给Spring Filter的。
> 这样所有发给Servlet的Filter请求都是通过DelegationFilterProxy委托给了Spring Filter来进行拦截处理。这样就实现了Spring来管理Filter的功能。
从上面我们可以看出springSecurityFilterChain(FilterChainProxy)在某种程度上就是DelegatingFilterProxy在spring中的bean名。实际上DelegatingFilterProxy会生成一个FilterChainProxy对象,且在这个对象中,包含了security的过滤器链。

2.3 Filter的生成

上面解决了Servlet和Spring的Filter不一致问题,那下面就该考虑Spring如何生成Filter:在spring中FIlter实际上是通过HttpSecurity这个入口一步一步自动创建的。于是Spring Security就引出了第二个配置项:WebSecurityConfigurerAdapter、HttpSecurity

```
@Configuration
@EnableWebSecurityConfigureAdaptor
public class SecurityConfig extends WebSecurityConfigureAdaptor
{
	protected void configure(HttpSecurity http) throws Exception {
			logger.debug("Using default configure(HttpSecurity). 
			If subclassed this will potentially override subclass configure(HttpSecurity).");
	 
			http
				.authorizeRequests()		//过滤器1		FilterSecurityInterceptor
					.anyRequest()			//URL匹配,对哪些URL应用本过滤器
					.authenticated()		//过滤器的具体逻辑实现——启用认证功能	
				.and()
				.formLogin().and()		//过滤器2		UsernamePasswordAuthenticationFilter
				.httpBasic();			//过滤器3			BasicAuthenticationFilter
		}
}
```

HttpSecurity是SecurityBuilder接口的一个实现类,从名字上我们就可以看出这是一个HTTP安全相关的构建器。当然我们在构建的时候可能需要一些配置,当我们调用HttpSecurity对象的方法时,实际上就是在进行配置。包括Filter的创建,以及对于该FIlter而言的授权功能(access、has_role等)的创建,认证过程(authenticated)的创建。

authorizeRequests(),formLogin()、httpBasic()这三个方法返回的分别是ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer、HttpBasicConfigurer,他们都是SecurityConfigurer接口的实现类,分别代表的是不同类型的安全配置器。

实际上这些SecurityConfigurer接口的实现类就是实现将Filter注入到Spring容器中的配置类。在HttpSecurity中内部维护了一个List<Filter>,每新增一个Filter都会加入到该List中,这实际上就是FilterChainProxy的实际委托对象。
ExpressionUrlAuthorizationConfigurer、FormLoginConfigurer、HttpBasicConfigurer三个配置器对应的Filter分别是FilterSecurityInterceptor、UsernamePasswordAuthenticationFilter、BasicAuthenticationFilter。

2.4 认证、授权、数据实体

Filter的创建问题解决了,下面就该考虑具体的逻辑实现,而这无外乎就是认证(authenticate)和授权(authorize) 以及实体数据对象(userdetailes) 之间的关系
总体来看,认证-授权-实体对象 是通过如下方式连接起来的,UserDetailsService/UserDetails(认证和实体对象的连接 Interface)AuthenticationManager/Authentication(认证后对象和授权的连接 Interface)。
下面根据配置代码具体分析

```
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()								1.                                                                
            .antMatchers("/resources/**", "/signup", "/about").permitAll()  			2.                
            .antMatchers("/admin/**").hasRole("ADMIN")                                3.
            .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")      4.
            .anyRequest().authenticated()               5.                                    
            .and()
        // ...
        .formLogin();
}
/**
*   1、http.authorizeRequests()方法有很多子方法,每个子匹配器(antMatchers)将会按照声明的顺序起作用。上面我们知道http.authorizeRequests()方法实际上会产生一个FilterSecurityInterceptor,相当于这个过滤器定义了多个针对不同URL模式的认证过程

	2、指定用户可以访问的多个url模式。特别的,任何用户可以访问以"/resources"开头的url资源,或者等于"/signup"或about
	
	3、任何以"/admin"开头的请求限制用户具有 "ROLE_ADMIN"角色。你可能已经注意的,尽管我们调用的hasRole方法,但是不用传入"ROLE_"前缀
	
	4、任何以"/db"开头的请求同时要求用户具有"ROLE_ADMIN"和"ROLE_DBA"角色。
	
	5、任何没有匹配上的其他的url请求,只需要用户被验证。
*/
```

实际上授权一般来说是对认证的功能进一步增强。认证只是验证用户是否正确,而授权则是更高级别的用户权限验证。对于2来说不需要进行认证过程,而对于3.4来说不仅需要授权还需要认证,对于5来说就只需要认证,不需要授权。

// 这就是上面调用authenticated()所操作的实际Authentication方法
@Autowiredprivate DataSource dataSource;
@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .jdbcAuthentication()	//基于JDBC验证(DaoAuthenticationProvider)  //.inMemoryAuthentication(基于内存验证 DaoAuthenticationProvider)  //.ldapAuthentication(基于LDAP验证 LdapAuthenticationProvider)
                        .dataSource(dataSource)	//接入数据库
                        .userByUsernameQuery("select username ,password,true"+"from Spitter where username=?")	//自定义认证查询方式
                        .withDefaultSchema()
                        .withUser("user").password("password").roles("USER").and()
                        .withUser("admin").password("password").roles("USER", "ADMIN");
}

让我们具体来看一下这几个模块

2.4.1 认证(authenticate)/用户实体(UserDetails)

认证(authenticate)功能的创建
如5所示,当过滤器调用authenticated()时就相当于开启了认证功能,该过滤器(FilterSecurityInterceptor)会判断当前对象是否是已被认证后的登录态(在到达这个filter之前会经过其他专门用于认证的filter,从http中解析出用户数据,并交给相应的AuthenticationProvider去处理,如果认证失败会被AnonymousAuthenticationFilter所处理,其Authentication的值将被设为AnonymousAuthenticationToken存放于securityContext中)。其内部实现机制实际上是如下方式运转的。

  1. Spring会帮我们自动注入AuthenticationManagerBuilder,而AuthenticationManagerBuilder会帮助我们创建一个AuthenticationManage(接口)的子类实例ProviderManager,其是用于管理AuthenticationProvider(接口)的,相当于最终的认证过程都是AuthenticationProvider的具体实现类(如上面的DaoAuthenticationProvider)完成的。也就是说实际上对我们而言用到的实例就是ProviderManager和具体Provider
  2. 认证是由AuthenticationManager来管理的,但是真正进行认证的是AuthenticationManager中定义的AuthenticationProvider。AuthenticationManager中可以定义有多个AuthenticationProvider。当我们使用authentication-provider元素来定义一个AuthenticationProvider时,如果没有指定对应关联的AuthenticationProvider对象,Spring Security默认会使用DaoAuthenticationProvider。
  3. AuthenticationManagerBuilder的inMemoryAuthentication()、jdbcAuthentication()、ldapAuthentication()这三个方法分别返回的
    InMemoryUserDetailsManagerConfigurer、JdbcUserDetailsManagerConfigurer、LdapAuthenticationProviderConfigurer三个SecurityConfiguer子类实例,这些Configure会自动创建对应的provider。另外上面我们说过认证离不开具体实例对象,所以DaoAuthenticationProvider在进行认证的时候需要一个UserDetailsService来获取用户的信息UserDetails,其中包括用户名、密码和所拥有的权限(GrantedAuthority)等。所以上面三个Configurer类也会帮助我创建各自对应的UserDetailsService实例。
    在这里插入图片描述在这里插入图片描述
    图片来源:http://www.tianshouzhi.com/api/tutorials/spring_security_4/264
    从上图我们可看出由AuthenticationManagerBuilder生成的不同的Provider他们各自还对应一个UserDetailsService的三个子类实例:InMemoryUserDetailsManager、JdbcUserDetailsManager、LdapUserDetailManager。这几个UserDetailsService实例都是由其configure类自动创建并注入到Spring中的。
  4. 所以如果我们需要改变认证的方式,我们可以实现自己的AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现UserDetailsService(实体数据接口,相当于DAO)。比如说当用户存储在MongoDB或者一些未在Spring所支持的用户存储类型时,我们就需要自定义实现。
    而自定义实现,从上面我们已经知道了只需实现UserDetailsService接口,和AuthenticationProvider接口。而于我们而言,需要看的是UserDetailsService接口的实现。
//spring定义的UserDetailsService接口
public interfce UserDetailsService{
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

下面引用spring 实战4中的自定义UserDetailsService接口实现代码

在这里插入图片描述

上面的代码自定义了SpitterUserService这个实现类,重载了loadUserByUsername方法,另外这份代码值得借鉴的一点是,它把DAO和UserDetailsService进一步解耦,SpitterUserService并不知道用户数据存储在什么地方。设置进来的SpitterRepository 能够从关系型数据库、文档数据库或图数据中查找Spitter 对象,甚至可以伪造一个。SpitterUserService 不知道也不会关心底层所使用的数据存储。它只是获得Spitter对象,并使用它来创建User 对象。(User 是UserDetails 的具体实现。另外还可以考虑让Spitter对象实现UserDetails,这样就不必在DetailsService层封装成UserDetails )

另外对于AuthenticationProvider接口,并不需要我们自己代码实现,只需要通过如下方式将上面的SpitterUserService 装配进去即可。这样他就会自动生成自定义的Provider。

//通过xml方式
<authentication-manager>
    <authentication-provider user-service-ref='myUserDetailsService'/>
</authentication-manager>

/**
*通过java配置
*userDetailsService() 方法(类似于jdbcAuthentication() 、ldapAuthentication 以及inMemoryAuthentication() )会配置一个用户存储。
*不过,这里所使用的不是Spring所提供的用户存储,而是使用UserDetailsService 的实现。
*/
@AutoWired
SpitterRepository spitterRepository;
@override
protected void configure(AuthenticationManager auth) throws Excption{
	auth.userDetailsService(new SpittterUserService)
}
2.4.2 授权(authority)

上面说了Authentication和UserDetails,接下来探讨授权(authority)。这里直接引用官网的一句话:

The main interface responsible for making access-control decisions in Spring Security is the AccessDecisionManager. It has a decide method which takes an Authentication object representing the principal requesting access, a “secure object” (see below) and a list of security metadata attributes which apply for the object (such as a list of roles which are required for access to be granted).

翻译一下:就是说负责授权的核心就是AccessDecisionManager类,它有一个decision方法,该方法的处理对象就是Authentication实例对象和一个受保护对象(如某些需要验证和授权才能访问的资源对象)以及一系列应用到这个secure object上的安全元数据参数(比如说对于授权必要的角色权限控制列表,补充说一句,相当于如果这个Authentication实例对象的role在这个列表中,即授权通过)。
下面我们再直接引用官网的说明:

Each supported secure object type has its own interceptor class, which is a subclass of AbstractSecurityInterceptor. Importantly, by the time the AbstractSecurityInterceptor is called, the SecurityContextHolder will contain a valid Authentication if the principal has been authenticated.

上面说的是每一个secure object都有一个自己的拦截器(实际上当声明了受保护对象后,就会自动创建这个拦截器,比如 http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN"),就会对所有为‘/admin/**’的url创建拦截器),这个拦截器是AbstractSecurityInterceptor的一个子类实例对象。AbstractSecurityInterceptor会在UserDetails验证通过并注入到SecurityContextHolder 中后进行调用。

实际上,所有的Authentication实现类都保存了一个GrantedAuthority列表,其表示用户所具有的权限.GrantedAuthority是通过AuthenticationManager设置到Authentication对象中的,然后AccessDecisionManager将从Authentication中获取用户所具有的GrantedAuthority来鉴定用户是否具有访问对应资源的权限。

AbstractSecurityInterceptor provides a consistent workflow for handling secure object requests, typically:

  1. Look up the “configuration attributes” associated with the present request
  2. Submitting the secure object, current Authentication and configuration attributes to the AccessDecisionManager for an authorization decision
  3. Optionally change the Authentication under which the invocation takes place
  4. Allow the secure object invocation to proceed (assuming access was granted)
  5. Call the AfterInvocationManager if configured, once the invocation has returned. If the invocation raised an exception, the AfterInvocationManager will not be invoked.

我们从上面看出AbstractSecurityInterceptor 实例对象会按照如下工作流进行权限控制:

  1. 查找和当前请求相关的配置参数
    http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")中的.hasRole(“ADMIN”)即其配置参数
    又或者是xml配置中的 <intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>

  2. 当UserDetails认证通过后,封装成Authentication对象,将相关配置参数和当前Authentication对象一起传给AccessDecisionManager 做决定。

  3. 这里说的是在授权后可以对Authentication对象做出改变。引用官网的一段话:

    Assuming AccessDecisionManager decides to allow the request, the AbstractSecurityInterceptor will normally just proceed with the request. Having said that, on rare occasions users may want to replace the Authentication inside the SecurityContext with a different Authentication, which is handled by the AccessDecisionManager calling a RunAsManager. This might be useful in reasonably unusual situations, such as if a services layer method needs to call a remote system and present a different identity. Because Spring Security automatically propagates security identity from one server to another (assuming you’re using a properly-configured RMI or HttpInvoker remoting protocol client), this may be useful.

    翻译一下,这种情况并不是很常见,但是如果一个服务请求在本系统授权后,需要以另一个不同的身份去调用另一个远程系统调用时,这就很有用。这可以通过AccessDecisionManager 的RunAsManager实现。

  4. 授权后, secure object 可以继续被Authentication访问。(ps:感觉这个说了当白说)

  5. 解释一下,AccessDecisionManager是用来在访问受保护对象之前判断用户是否拥有访问该对象的权限。有的时候我们可能会希望在请求执行完成后对返回值做一些修改,当然,你可以简单的通过AOP来实现这一功能。Spring Security为我们提供了一个AfterInvocationManager接口,它允许我们在受保护对象访问完成后对返回值进行修改或者进行权限鉴定,看是否需要抛出AccessDeniedException,其将由AbstractSecurityInterceptor的子类实例进行调用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值