参考以下文章整理的笔记:web
#1 Authentication认证信息spring
认证前:用来表示用户输入的认证信息的对象安全
认证后:包含用户详细信息以及权限信息的对象cookie
接口定义以下:session
public interface Authentication extends Principal, Serializable { //获取用户权限 Collection<? extends GrantedAuthority> getAuthorities(); //获取密码信息 Object getCredentials(); Object getDetails(); //获取用户惟一标示,如用户名 Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
接口实现以下:app
#2 SecurityContextHolder用来保存SecurityContext信息的jsp
SecurityContextHolder默认使用ThreadLocalSecurityContextHolderStrategy策略来保存。ide
ThreadLocalSecurityContextHolderStrategy策略实现就是使用ThreadLocal来保存post
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>(); }
SecurityContext又是用来保存上述Authentication信息,接口定义以下:ui
public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication authentication); }
#3 AuthenticationManager对用户输入信息进行认证
接口定义以下:
public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; }
接口实现以下:
默认实现ProviderManager,内部包含了一个List类型的AuthenticationProvider providers,也就是说ProviderManager委托内部的AuthenticationProvider来实现认证的
使用以下标签默认就是建立ProviderManager:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider user-service-ref="userDetailsService"/> <security:authentication-provider ref="xxxxx"/> </security:authentication-manager>
#4 AuthenticationProvider对用户输入信息进行认证
接口的定义以下:
public interface AuthenticationProvider { Authentication authenticate(Authentication authentication)throws AuthenticationException; boolean supports(Class<?> authentication); }
接口实现以下:
默认实现DaoAuthenticationProvider:
内部含有一个UserDetailsService userDetailsService对象,用来加载用户信息包含权限信息
1 使用UserDetailsService userDetailsService来加载用户信息到UserDetails对象中
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
所以在UserDetailsService上述加载过程实现中,咱们就能够根据用户名来判断用户是否存在。
UserDetails接口实现,默认就是org.springframework.security.core.userdetails.User,因此咱们通常须要继承该对象
2 检查上述用户信息是否过时、被锁定等等,分红preAuthenticationChecks,检查密码是否正确和postAuthenticationChecks
private class DefaultPreAuthenticationChecks implements UserDetailsChecker { public void check(UserDetails user) { if (!user.isAccountNonLocked()) { logger.debug("User account is locked"); throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), user); } if (!user.isEnabled()) { logger.debug("User account is disabled"); throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"), user); } if (!user.isAccountNonExpired()) { logger.debug("User account is expired"); throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), user); } } } protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } if (authentication.getCredentials() == null) { logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails); } String presentedPassword = authentication.getCredentials().toString(); if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails); } }
其中上述密码检查,接口是PasswordEncoder,默认是PlaintextPasswordEncoder,没有加密过的
private class DefaultPostAuthenticationChecks implements UserDetailsChecker { public void check(UserDetails user) { if (!user.isCredentialsNonExpired()) { logger.debug("User account credentials have expired"); throw new CredentialsExpiredException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"), user); } } }
3 一旦认证经过,构建一个UsernamePasswordAuthenticationToken对象,使用上述UserDetails信息、用户输入的密码信息
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; }
#5 认证过程
ExceptionTranslationFilter是用来处理来自AbstractSecurityInterceptor抛出的AuthenticationException和AccessDeniedException的
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); } if (ase != null) { handleSpringSecurityException(request, response, chain, ase); } else { // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); } } }
AbstractSecurityInterceptor是Spring Security用于拦截请求进行权限鉴定的,其拥有两个具体的子类,拦截方法调用的MethodSecurityInterceptor和拦截URL请求的FilterSecurityInterceptor。
当ExceptionTranslationFilter捕获到的是AuthenticationException时将调用AuthenticationEntryPoint引导用户进行登陆;
若是捕获的是AccessDeniedException,可是用户尚未经过认证,则调用AuthenticationEntryPoint引导用户进行登陆认证,不然将返回一个表示不存在对应权限的403错误码
1 AbstractSecurityInterceptor对用户访问资源进行检查,若是未认证或者没权限则抛出异常
2 ExceptionTranslationFilter这个Filter捕获上述异常信息,引导重定向到登录界面
3 用户输入用户名和密码进行认证登录
4 Spring Security将获取到的用户名和密码封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken
5 将上述产生的token对象传递给AuthenticationManager进行登陆认证。
6 AuthenticationManager认证成功后将会返回一个封装了用户权限等信息的Authentication对象
7 将上述Authentication对象封装到SecurityContext中
8 SecurityContextPersistentFilter将上述SecurityContext保存到对应的HttpSession属性中,key为:SPRING_SECURITY_CONTEXT
SecurityContextPersistentFilter在Filter执行前,从HttpSession中先尝试获取保存的SecurityContext对象信息,若是没有则建立一个。若是HttpSession没有的话,看SecurityContextPersistentFilter的forceEagerSessionCreation属性,若是强制产生,则在此处调用request的getSession方法产生HttpSession
Filter执行后,会将上述SecurityContext对象信息保存到HttpSession的属性中
所以,一样用户再次访问的时候,就不须要进行登陆认证了,只须要从HttpSession中就能取出对应的认证信息SecurityContext了
也就是说,用户在某一次请求的过程当中,SecurityContext是保存在ThreadLocal中的,可是请求结束后,就会被清除了。同时,SecurityContext也被保存在HttpSession中,这样的话,同一个用户不一样请求即便是在不一样的线程上,也都能根据session来获取到SecurityContext信息。
#6 Filter链
Spring Security已经定义了一些Filter,无论实际应用中你用到了哪些,它们应当保持以下顺序。
在web.xml中以下配置:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
DelegatingFilterProxy实际上是委托给Spring容器中的Filter来执行,如何指定哪个呢?采用DelegatingFilterProxy自身的targetBeanName这个参数
上述就是默认取值spring容器中的这个springSecurityFilterChain Filter,采用的是FilterChainProxy类
当咱们使用基于Spring Security的NameSpace进行配置时,系统会自动为咱们注册一个名为springSecurityFilterChain类型为FilterChainProxy的bean。该FilterChainProxy含有一个List<SecurityFilterChain> filterChains属性,咱们以下配置
Spring security容许咱们在配置文件中配置多个http元素,以针对不一样形式的URL使用不一样的安全控制。Spring Security将会为每个http元素建立对应的FilterChain,同时按照它们的声明顺序加入到FilterChainProxy。因此当咱们同时定义多个http元素时要确保将更具备特性的URL配置在前。
<security:http pattern="/login*.jsp*" security="none"/> <!-- http元素的pattern属性指定当前的http对应的FilterChain将匹配哪些URL,如未指定将匹配全部的请求 --> <security:http pattern="/admin/**"> <security:intercept-url pattern="/**" access="ROLE_ADMIN"/> </security:http> <security:http> <security:intercept-url pattern="/**" access="ROLE_USER"/> </security:http>
每个security:http都将对应一个SecurityFilterChain。每个SecurityFilterChain中都包含各自的Filter
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) request); HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response); List<Filter> filters = getFilters(fwRequest); if (filters == null || filters.size() == 0) { if (logger.isDebugEnabled()) { logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list")); } fwRequest.reset(); chain.doFilter(fwRequest, fwResponse); return; } VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters); vfc.doFilter(fwRequest, fwResponse); }
默认注册了以下Filter
SecurityContextPersistenceFilter
在一开始进行request的时候就能够在SecurityContextHolder中创建一个SecurityContext,而后在请求结束的时候,任何对SecurityContext的改变均可以被copy到HttpSession
LogoutFilter
检查是不是登出地址
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter会检查请求地址是否是配置的登录地址,若是是则进行认证检查,从request中获取username和password构建成UsernamePasswordAuthenticationToken,而后使用AuthenticationManager authenticationManager来进行认证过程,认证成功以后重定向到target地址
BasicAuthenticationFilter
RequestCacheAwareFilter
session中还能够保存着key为SPRING_SECURITY_SAVED_REQUEST的类型为DefaultSavedRequest的request信息,每次请求都要从request中获取session,若是没有则不会建立session,若是有session,则从session中获取DefaultSavedRequest的request信息,若是没有保存的话,则仍然为null,默认状况下,这个filter不起做用,由于没有向session中保存上述信息
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
若是当前用户的SecurityContext中的认证信息为null的话,建立一个默认的角色
SessionManagementFilter
ExceptionTranslationFilter
捕获以后程序执行中的AuthenticationException和AccessDeniedException。若是用户没有认证信息,则引导用户重定向到登录界面,若是已经有登录信息,则认为是权限不足,重定向到权限不足界面
FilterSecurityInterceptor
保护每个受访问的uri,一旦用户权限不足,则抛出AccessDeniedException异常
咱们能够看到整个过程与session打交道的时候仅仅是在SecurityContextPersistenceFilter中,请求完成以后,会将SecurityContext信息保存到session中而已。供下次使用的时候,直接从session中获取SecurityContext信息,此时的SecurityContext信息中含有用户的认证信息,所以访问一个资源的话,上述filter都不会拦截,在FilterSecurityInterceptor中也会被正常经过。
一旦session失效,不能从session中获取SecurityContext信息了,就会在FilterSecurityInterceptor抛出AccessDeniedException异常,而后被ExceptionTranslationFilter捕获,引导用户重定向到登录界面