Servlet安全
Filters
Spring Servlet基于过滤器链来保证网络安全的.
当客户端收到一个请求时,会创建一个过滤器链FilterChain,这其中包含了一系列的Filters和Servlet,所以不同URL会有不同过滤器.
这些过滤器主要:
- Filter通常将编写HttpServletResponse, 防止下游过滤器或Servlet被调用。
- 被下游的Servlet和Filters
HttpServletRequest和HttpServletResponse
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
DelegatingFilterProxy
是一个特殊的Filter,通过此类将Servlet标准的Filter与Spring Application容器中的bean建立桥接关系.可以通过此类将注册到spring容器中的Filter找出来,执行其中的doFilter
DelegatingFilterProxy从Spring容器中找出来注册的Filter,然后调用此Filter中逻辑
// 伪代码
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
Filter delegate = getFilterBean(someBeanName,Filter.class);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
FilterChainProxy
FilterChainProxy是Spring Security提供的特殊过滤器,允许通过
SecurityFilterChain委派许多过滤器实例。 由于FilterChainProxy是Bean,因此通常将其包装在DelegatingFilterProxy中。
SecurityFilterChain
FilterChainProxy通过SecurityFilterChain决定当前Request应该经过哪些过滤器,他的实现类就是DefaultSecurityFilterChain
private void doFilterInternal(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// ...
List<Filter> filters = getFilters(fwRequest);
// ...
// 创建针对此子请求的过滤器链
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
Handling Security Exceptions
认证授权过程中的异常处理
首先,ExceptionTranslationFilter调用FilterChain.doFilter(request,response)来调用应用程序的其余部分。
如果程序执行过程中出现认证异常或认证失败(AuthenticationException)
SecurityContextHolder(环境上下文)会被清除- HttpServletRequest保存在RequestCache中。 用户成功进行身份验证后,将使用RequestCache重放原始请求。
AuthenticationEntryPoint用于从客户端请求凭据。 例如,它可能重定向到登录页面或发送WWW-Authenticate标头。
如果是AccessDeniedException, 则调用AccessDeniedHandler
认证
SecurityContextHolder
SpringSecurity存储认证信息的地方,认证成功后,将认证信息加入其中
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
SpringSecurity默认使用ThreadLocal存储变量
Authentication
用于提供用户提供的用于身份验证的凭据。 在这种情况下使用时,isAuthenticated()返回false。代表当前经过身份验证的用户。 可以从SecurityContext获得当前的身份验证。
这玩意就是一般登录成功的时候将当前用户信息封装成Authentication,放入SecurityContextHolder中,以备以后使用
public interface Authentication extends Principal, Serializable {
// 权限
Collection<? extends GrantedAuthority> getAuthorities();
// 密码
Object getCredentials();
// 其他相关信息,如IP
Object getDetails();
// 用户名
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
Authentication接口的实现类,当然它还有很多的实现类,UsernamepasswordAuthenticationToken是它的最常用实现类之一
GrantedAuthentication
表示用户的权限信息
AuthenticationManager
AuthenticationManager是用于定义Spring Security的过滤器如何执行身份验证的API。 然后由调用AuthenticationManager的控制器(即Spring Security的Filters)在SecurityContextHolder上设置返回的身份验证。 如果没有与Spring Security的过滤器集成,则可以直接设置SecurityContextHolder,并且不需要使用AuthenticationManager。
虽然AuthenticationManager的实现可以是任何东西,但最常见的实现是ProviderManager。
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
ProviderManager
ProviderManager是最常用的AuthenticationManager实现类.
ProviderManager委托给AuthenticationProviders列表。 每个AuthenticationProvider都有机会指示认证应该成功,失败,或者表明它不能做出决定并允许下游AuthenticationProvider进行决定。 如果没有一个已配置的AuthenticationProviders可以进行身份验证,则身份验证将失败,并显示ProviderNotFoundException,这是一个特殊的AuthenticationException,它指示未配置ProviderManager支持传递给它的身份验证类型。
public class ProviderManager implements AuthenticationManager, MessageSourceAware,InitializingBean {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 遍历每个AuthenticationProvider
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
}catch (AccountStatusException e) {
//...
}
}
}
}
AuthenticationProvider
真正做做认证的接口
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
### AuthenticationEntryPoint > AuthenticationEntryPoint用于从客户端请求登录凭据的HTTP响应
客户端将向未经授权访问的资源发出未经身份验证的请求。 在这种情况下,AuthenticationEntryPoint的实现用于从客户端请求凭据。 AuthenticationEntryPoint实现可能会执行重定向到登录页面,使用WWW-Authenticate标头进行响应等。
AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter用作基础过滤器,用于验证用户的凭据。 在对凭证进行身份验证之前,Spring Security通常使用AuthenticationEntryPoint请求凭证。
接下来,AbstractAuthenticationProcessingFilter可以对提交给它的任何身份验证请求进行身份验证。
- 首先当一个request经过
AbstractAuthenticationProcessingFilter时,比如他的实现类UsernamePasswordAuthenticationFilter时,会将username,password封装成UsernamePasswordAuthenticationToken(Authentication的实现类) - 获取
AuthenticationManager,这里一般是ProviderManager,进行认证 - 如果成功
SessionAuthenticationStrategy收到新的登录通知- SecurityContextHolder将保存登录信息,稍后SecurityContextPersistenceFilter将登录信息保存到Session中
- RememberMeServices.loginSuccess被调用。 如果记住我未配置,则为空。
- ApplicationEventPublisher将会发送
InteractiveAuthenticationSuccessEvent事件 - 调用
AuthenticationSuccessHandler中登录成功逻辑
- 如果失败
UsernamePasswordAuthentication
验证用户身份的最常见方法之一是验证用户名和密码。 这样,Spring Security为使用用户名和密码进行身份验证提供了全面的支持。
基于username/password的认证方式有
Form Login
- 首先访问一个需要验证的请求
FilterSecurityInterceptor会拦截未认证的请求,并且抛出一个AccessDeniedException- 由于未对用户进行身份验证,因此ExceptionTranslationFilter会启动“开始身份验证”,并使用配置的AuthenticationEntryPoint将重定向发送到登录页面。 在大多数情况下,
AuthenticationEntryPoint是LoginUrlAuthenticationEntryPoint的实例。 - 然后,浏览器将请求将其重定向到的登录页面
- 程序内部包含了登录页面,将登录页面渲染到前端
Basic Login
UserDetails
UserDetails通过UserDetailsService返回.DaoAuthenticationProvider.DaoAuthenticationProvider验证UserDetails,然后返回具有主体的Authentication,该主体是配置的UserDetailsService返回的UserDetails。
这是他的接口信息
public interface UserDetails extends Serializable {
// ~ Methods
// ========================================================================================================
// 获取权限
Collection<? extends GrantedAuthority> getAuthorities();
// 获取密码
String getPassword();
// 获取用户名
String getUsername();
// 账号是否过期
boolean isAccountNonExpired();
// 是否锁定
boolean isAccountNonLocked();
// 密码是否过期
boolean isCredentialsNonExpired();
// 用户是否可用
boolean isEnabled();
}
org.springframework.security.core.userdetails.User是其常用的实现类
UserDetailsService
DaoAuthenticationProvider使用UserDetailsService检索用户名,密码和其他用于使用用户名和密码进行身份验证的属性。 Spring Security提供
UserDetailsService的内存中和JDBC实现.
### PasswordEncoder > 圈起来的为自适应密码哈希算法
授权
Authorities
权限接口:
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
AccessDecisionManager
AbstractSecurityInterceptor通过调用AccessDecisionManager,由AccessDecisionManager决定请求的最终访问权限
SpringSecurity提供了大量的控制权限访问的拦截器去保障某些对象的安全,像方法调用啊,web请求等等.而AccessDecisionManager就决定这个请求,调用是不是会调用成功
基于AccessDecisionManager实现类的投票
他们之间的类图关系
以AffirmativeBased为例
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}
// 抛异常
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
// 检查是不是允许所有投票者都弃权
checkAllowIfAllAbstainDecisions();
}
通过一系列AccessDecisionVoter的实现类不断轮询投票,然后AccessDecisionManager基于投票结果决定是否抛出AccessDeniedException
每个AccessDecisionVoter有三中结果
- ACCESS_GRANTED: 同意访问
- ACCESS_ABSTAIN:如果对授权决定没有意见,就投此票,
- ACCESS_DENIED:拒绝访问
RoleVoter
基于角色的投票者,以
ROLE_开头的权限,用此投票
AuthenticatedVoter
用于匿名,完全认证和记住我的认证用户的权限控制,用此投票
当然.可以继承AccessDecisionVoter进行定制投票器
After Invocation Handling
虽然在进行安全对象调用之前AbstractSecurityInterceptor会调用AccessDecisionManager,但某些应用程序需要一种方法来修改安全对象调用实际返回的对象。 尽管可以轻松实现自己的AOP问题来实现这一点,但Spring Security提供了一个方便的挂钩,该挂钩具有几种与其ACL功能集成的具体实现。
授权过程
- 首先
FilterSecurityInterceptor从SecurityContextHolder中获取Authentication FilterSecurityInterceptor创建FilterInvocation(包含HttpServletRequest,HttpServletResponse,FilterChain)- 将
FilterInvocation传递给SecurityMetaDataSource获取ConfigAttributes - 将
FilterInvocation,Authentication,ConfigAttributes传递给AccessDecisionManager- 如果授权失败,则抛出
AccessDeniedException,然后ExceptionTranslationFilter处理这个异常 - 如果授权成功,则
FilterSecurityInteceptor继续执行FilterChain后面逻辑
- 如果授权失败,则抛出
参考文档