1.结构总览
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截, 校验每个请求是否能够访问它所期望的资源。可以通过Filter或AOP等技术来实现,Spring Security对Web资源的保护是靠Filter实现的,所以从这个Filter来入手,逐步深入Spring Security原理。 当初始化Spring Security时,会创建一个名为 SpringSecurityFilterChain 的Servlet过滤器,类型为 org.springframework.security.web.FilterChainProxy,它实现了javax.servlet.Filter,因此外部的请求会经过此 类,下图是Spring Security过滤器链结构图:
FilterChainProxy是一个代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各个Filter,同时 这些Filter作为Bean被Spring管理,它们是Spring Security核心,各有各的职责,但他们并不直接处理用户的认 证,也不直接处理用户的授权,而是把它们交给了认证管理器(AuthenticationManager)和决策管理器 (AccessDecisionManager)进行处理,下图是FilterChainProxy相关类的UML图示
UsernamePasswordAuthenticationFilter:用于处理认证,必须提交用户名和密码封装成Authentication类,但真正处理的是AuthenticationManager。 FilterSecurityInterceptor:用于处理授权,保护web资源,真正处理是AccessDecisionManager。
2.springsecurity认证
根据上面的UML图可以得出springsecurity核心就是责任链模式下的一个个过滤器,其中对认证起关键作用的就是AuthenticationManager认证管理器,下面是整个认证流程:
- UsernamePasswordAuthenticationFilter:将用户名密码封装成Authentication对象,然后传递给AuthenticationManager(关键认证处理器);
- AuthenticationManager:(实现类:providerManager):交由AuthenticationProvider认证authenticate();
- AuthenticationProvider:(实现类:DaoAuthenticationProvider):用于委托认证authenticate(),调用UserDetailsService去获取信息;
- UserDeatilsService:(实现类:InMemoryUserDetailsManager内存认证等):根据不同的存储媒介如内存或数据库去拿到用户信息;
- PasswordEncoder:(实现类:NoOpPasswordEncoder等):用于密码比对,可以选择直接字符串比较或者加密比较处理;
- SecurityContextHolder:上下文对象用于保存会话;
这就是完整的一个认证,我们可以根据用不同实现类引入容器来实现我们想要的处理逻辑,如从内存或数据库读取数据,选择对应的UserDetailsService实现类即可。
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
3.springsecurity授权
springsecurity授权拦截如下:
- FilterSecurityInterceptor:用于接受已认证用户请求资源拦截,然后交由SecurityMetadataSource处理。
- Securitymeatadatasource:(实现类:DefaultFilterInvocationSecurityMetadataSource)用于获取访问当前资源的权限集合。
- AccessDecisionManager:(Spring Security内置了三个基于投票的AccessDecisionManager实现类如下,它们分别是AffirmativeBased、ConsensusBased和UnanimousBased)采用投票的方式来确定是否能够访问受保护资源。
- AccessDecisionVoter:(实现类如RoleVoter、AuthenticatedVoter和WebExpressionVoter等)不同的投票者决定不同投票方式。
授权的方式包括 web授权和方法授权,web授权是通过url拦截进行授权,方法授权是通过方法拦截进行授权。他们都会调用accessDecisionManager进行授权决策,若为web授权则拦截器为FilterSecurityInterceptor;若为方法授权则拦截器为MethodSecurityInterceptor。如果同时通过web授权和方法授权则先执行web授权,再执行方 法授权,最后决策通过,则允许访问资源,否则将禁止访问。
3.1.web授权
通过给 http.authorizeRequests() 添加多个子节点来定制需求到我们的URL,如下代码:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() (1)
.antMatchers("/r/r1").hasAuthority("p1") (2)
.antMatchers("/r/r2").hasAuthority("p2") (3)
.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')") (4)
.antMatchers("/r/**").authenticated() (5)
.anyRequest().permitAll() (6)
.and()
.formLogin()
// ...
}
(1)http.authorizeRequests() 方法有多个子节点,每个macher按照他们的声明顺序执行。 (2)指定"/r/r1"URL,拥有p1权限能够访问 (3)指定"/r/r2"URL,拥有p2权限能够访问 (4)指定了"/r/r3"URL,同时拥有p1和p2权限才能够访问 (5)指定了除了r1、r2、r3之外"/r/x "资源,同时通过身份认证就能够访问,这里使用SpEL(Spring Expression Language)表达式。。 (6)剩余的尚未匹配的资源,不做保护。
3.2.方法授权
从Spring Security2.0版本开始,它支持服务层方法的安全性的支持。@PreAuthorize,@PostAuthorize, @Secured三类注解。 我们可以在任何 @Configuration 实例上使用 @EnableGlobalMethodSecurity 注释来启用基于注解的安全性。
4.会话
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管 理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取 用户身份,SpringSecurity获取当前登录用户信息的方法为SecurityContextHolder.getContext().getAuthentication()
4.1会话控制
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管 理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取 用户身份,SpringSecurity获取当前登录用户信息的方法为SecurityContextHolder.getContext().getAuthentication()
机制 | 描述 |
---|---|
always | 如果需要就创建一个Session(默认)登录时 |
ifRequired | 如果需要就创建一个Session(默认)登录时 |
never | SpringSecurity 将不会创建Session,但是如果应用中其他地方创建了Session,那么SpringSecurity将会使用它。 |
stateless | SpringSecurity将绝对不会创建Session,也不使用Session |
通过以下配置方式对该选项进行配置:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
}
默认情况下,Spring Security会为每个登录成功的用户会新建一个Session,就是ifRequired 。 若选用never,则指示Spring Security对登录成功的用户不创建Session了,但若你的应用程序在某地方新建了 session,那么Spring Security会用它的。 若使用stateless,则说明Spring Security对登录成功的用户不会创建Session了,你的应用程序也不会允许新建 session。并且它会暗示不使用cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于REST API 及其无状态认证机制。
评论