目录

认证过程

AuthenticationManager

Authentication

AbstractAuthenticationToken

UsernamePasswordAuthenticationToken

RememberMeAuthenticationToken

AbstractOAuth2TokenAuthenticationToken

ProviderManager

AuthenticationProvider

DaoAuthenticationProvider

AbstractUserDetailsAuthenticationProvider

UserDetails

UserDetailsChecker

DaoAuthenticationProvider属性

DaoAuthenticationProvider.retrieveUser()

createSuccessAuthentication()

additionalAuthenticationChecks()

UserDetailsService

JdbcUserDetailsManager

InMemoryUserDetailsManager

总结

引入

AuthenticationManagerBuilder

performBuild()

Filters

UsernamePasswordAuthenticationFilter

AbstractAuthenticationProcessingFilter

attemptAuthentication

AnonymousAuthenticationFilter

ExceptionTranslationFilter

FilterSecurityInterceptor

AbstractSecurityInterceptor

AccessDecisionManager

AbstractAccessDecisionManager

AccessDecisionVoter

WebExpressionVoter

RoleVoter

AuthenticatedVoter

AffirmativeBased

ConsensusBased

UnanimousBased

密码验证时序图


Spring Security解决用户认证(Authentication)和用户授权(Authorization)2个问题。

源码地址:https://github.com/spring-projects/spring-security/tree/5.2.1.RELEASE

认证过程

AuthenticationManager

该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数;

public interface AuthenticationManager {Authentication authenticate(Authentication authentication)           throws AuthenticationException;
}

Authentication

Authentication封装了验证请求信息。

public interface Authentication extends Principal, Serializable {//获取 授权信息Collection<? extends GrantedAuthority> getAuthorities();//凭据Object getCredentials();//用户信息Object getDetails();//主体,可以理解验证时的用户名。Object getPrincipal();//是否被认证。boolean isAuthenticated();//认证结果设置。void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

 授权信息:

public interface GrantedAuthority extends Serializable {String getAuthority();
}

根据不同的认证类型,定义了多种实现:

AbstractAuthenticationToken

 private final Collection<GrantedAuthority> authorities;private Object details;private boolean authenticated = false;

UsernamePasswordAuthenticationToken

 private final Object principal;private Object credentials;

RememberMeAuthenticationToken

 private final Object principal;private final int keyHash;

AbstractOAuth2TokenAuthenticationToken

 private Object principal;private Object credentials;private T token;

ProviderManager

它是 AuthenticationManager 的一个实现类,提供了基本的认证逻辑和方法;它包含了一个 List<AuthenticationProvider> 对象,通过 AuthenticationProvider 接口来扩展出不同的认证提供者(当Spring Security默认提供的实现类不能满足需求的时候可以扩展AuthenticationProvider 覆盖supports(Class<?> authentication)方法);

 private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
//验证者 列表private List<AuthenticationProvider> providers = Collections.emptyList();protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private AuthenticationManager parent;private boolean eraseCredentialsAfterAuthentication = true;

AuthenticationManager 通过 authenticate(Authentication) 方法对其进行验证;AuthenticationProvider实现类用来支撑对 Authentication 对象的验证动作;UsernamePasswordAuthenticationToken实现了 Authentication主要是将用户输入的用户名和密码进行封装,并供给 AuthenticationManager 进行验证;验证完成以后将返回一个认证成功的 Authentication 对象;

public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;AuthenticationException parentException = null;Authentication result = null;Authentication parentResult = null;boolean debug = logger.isDebugEnabled();//循环每个AuthenticationProvider for (AuthenticationProvider provider : getProviders()) {//如果不支持Authentication,则继续下一个if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}try {//验证result = provider.authenticate(authentication);//如果验证不为null,则表示验证成功。if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException | InternalAuthenticationServiceException e) {prepareException(e, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;} catch (AuthenticationException e) {lastException = e;}}//如果所有provider都不支持验证,则使用parent验证。if (result == null && parent != null) {// Allow the parent to try.try {result = parentResult = parent.authenticate(authentication);}catch (ProviderNotFoundException e) {// ignore as we will throw below if no other exception occurred prior to// calling parent and the parent// may throw ProviderNotFound even though a provider in the child already// handled the request}catch (AuthenticationException e) {lastException = parentException = e;}}//擦除敏感信息。if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {((CredentialsContainer) result).eraseCredentials();}// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published itif (parentResult == null) {eventPublisher.publishAuthenticationSuccess(result);}return result;}// Parent was null, or didn't authenticate (or throw an exception).if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published itif (parentException == null) {prepareException(lastException, authentication);}throw lastException;}

AuthenticationProvider

AuthenticationProvider提供用户认证。

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication)          throws AuthenticationException;boolean supports(Class<?> authentication);
}

Spring Security提供了以下认证方式:

  • 用户名,密码
  • LDAP
  • 匿名方式
  • RememberMe
  • JWT
  • OAuth2
  • ... ...

DaoAuthenticationProvider

DaoAuthenticationProvider支持Authentication为UsernamePasswordAuthenticationToken的认证。

 //AbstractUserDetailsAuthenticationProvider方法public boolean supports(Class<?> authentication) {return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));}

AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProviderDaoAuthenticationProvider提供了基本的认证方法。

public abstract class AbstractUserDetailsAuthenticationProvider implementsAuthenticationProvider, InitializingBean, MessageSourceAware {protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private UserCache userCache = new NullUserCache();private boolean forcePrincipalAsString = false;protected boolean hideUserNotFoundExceptions = true;private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
}

认证过程如下:

public Authentication authenticate(Authentication authentication)throws AuthenticationException {
//T0:Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));//T1:String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();
//T2:boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);
//T3:if (user == null) {cacheWasUsed = false;try {
//T4:user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException notFound) {logger.debug("User '" + username + "' not found");if (hideUserNotFoundExceptions) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}else {throw notFound;}}Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract");}try {
//T5:preAuthenticationChecks.check(user);
//T6:additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}catch (AuthenticationException exception) {if (cacheWasUsed) {// There was a problem, so try again after checking// we're using latest data (i.e. not from the cache)cacheWasUsed = false;user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);preAuthenticationChecks.check(user);additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);}else {throw exception;}}
//T7:postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}
//T8:return createSuccessAuthentication(principalToReturn, authentication, user);}

T0:判断是否是UsernamePasswordAuthenticationToken。

T1:获取userName,默认:NONE_PROVIDED

T2:从缓存中获取用户信息。

T3:如果缓存中没有用户信息,则通过retrieveUser获取(由子类实现)。

T4:通过retrieveUser获取用户信息。

T5:验证前check。

T6:附加check(由子类实现)。

T7:验证

T8:验证成功,创建Authentication。重新 new 了一个 UsernamePasswordAuthenticationToken,因为到这里认证已经通过了,所以将 authorities 注入进去,并设置 authenticated 为 true,即不再需要认证。

 protected abstract UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;protected abstract void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException;

UserDetails

UserDetails封装认证用户的详细信息。

public interface UserDetails extends Serializable {//权限Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();
//是否账户过期boolean isAccountNonExpired();
//是否lockedboolean isAccountNonLocked();
//凭据过期boolean isCredentialsNonExpired();
//账户是否可用boolean isEnabled();
}

UserDetailsChecker

验证用户。

  • AccountStatusUserDetailsChecker

验证用户状态。是否可用,过期,锁定,凭据是否过期

  • DefaultPostAuthenticationChecks

验证用户状态。凭据是否过期。

  • DefaultPreAuthenticationChecks

验证用户状态。是否可用,过期,锁定

DaoAuthenticationProvider属性

private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";private PasswordEncoder passwordEncoder;private volatile String userNotFoundEncodedPassword;private UserDetailsService userDetailsService;private UserDetailsPasswordService userDetailsPasswordService;

DaoAuthenticationProvider.retrieveUser()

protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {
//T1:UserDetailsService加载用户信息UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}

createSuccessAuthentication()

验证后对密码进行加密。

 @Overrideprotected Authentication createSuccessAuthentication(Object principal,Authentication authentication, UserDetails user) {boolean upgradeEncoding = this.userDetailsPasswordService != null&& this.passwordEncoder.upgradeEncoding(user.getPassword());if (upgradeEncoding) {String presentedPassword = authentication.getCredentials().toString();String newPassword = this.passwordEncoder.encode(presentedPassword);user = this.userDetailsPasswordService.updatePassword(user, newPassword);}return super.createSuccessAuthentication(principal, authentication, user);}

additionalAuthenticationChecks()

通过用户名,密码进行验证。

@SuppressWarnings("deprecation")protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}String presentedPassword = authentication.getCredentials().toString();if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}}

UserDetailsService

通过用户名获取用户信息。

public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

JdbcUserDetailsManager

基于JDBC访问UserDetail。默认需要包括以下表结构:

users (username, password, enabled)
authorities (username, authority) 
group_authorities (group_id, authority)
groups (id,group_name)
group_members (group_id, username)

InMemoryUserDetailsManager

基于内存访问UserDetails

总结

UserDetailsService接口作为桥梁,是DaoAuthenticationProvier与特定用户信息来源进行解耦的地方,UserDetailsServiceUserDetailsUserDetailsManager所构成;UserDetailsUserDetailsManager各司其责,一个是对基本用户信息进行封装,一个是对基本用户信息进行管理;

特别注意UserDetailsServiceUserDetails以及UserDetailsManager都是可被用户自定义的扩展点,我们可以继承这些接口提供自己的读取用户来源和管理用户的方法,比如我们可以自己实现一个 与特定 ORM 框架,比如 Mybatis 或者 Hibernate,相关的UserDetailsServiceUserDetailsManager

引入

@Import({ WebSecurityConfiguration.class,SpringWebMvcImportSelector.class,OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {boolean debug() default false;
}
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
public class AuthenticationConfiguration {private ApplicationContext applicationContext;private AuthenticationManager authenticationManager;private boolean authenticationManagerInitialized;private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();private ObjectPostProcessor<Object> objectPostProcessor;
}

AuthenticationManagerBuilder的实现类DefaultPasswordEncoderAuthenticationManagerBuilder 

 @Beanpublic AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);if (authenticationEventPublisher != null) {result.authenticationEventPublisher(authenticationEventPublisher);}return result;}

AuthenticationManagerBuilder

 private AuthenticationManager parentAuthenticationManager;private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();private UserDetailsService defaultUserDetailsService;private Boolean eraseCredentials;private AuthenticationEventPublisher eventPublisher;

performBuild()

构造ProviderManager

 protected ProviderManager performBuild() throws Exception {if (!isConfigured()) {logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");return null;}ProviderManager providerManager = new ProviderManager(authenticationProviders,parentAuthenticationManager);if (eraseCredentials != null) {providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);}if (eventPublisher != null) {providerManager.setAuthenticationEventPublisher(eventPublisher);}providerManager = postProcess(providerManager);return providerManager;}

Filters

登录过程中会提供很多Filter用于处理登录请求。认证相关的主要包含后面描述的几个

UsernamePasswordAuthenticationFilter

基于用户名和密码的认证Filter。

AbstractAuthenticationProcessingFilter

属性

protected ApplicationEventPublisher eventPublisher;protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
//private AuthenticationManager authenticationManager;protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//RememberMeServices private RememberMeServices rememberMeServices = new NullRememberMeServices();
//请求匹配器private RequestMatcher requiresAuthenticationRequestMatcher;private boolean continueChainBeforeSuccessfulAuthentication = false;private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();private boolean allowSessionCreation = true;
//认证成功handlerprivate AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
//认证失败handlerprivate AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

doFilter()

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;
//如果请求不匹配Filter,则使用原始chain。if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}Authentication authResult;try {
//尝试认证authResult = attemptAuthentication(request, response);
//结果为null,表示认证失败。if (authResult == null) {// return immediately as subclass has indicated that it hasn't completed// authenticationreturn;}
//sessionStrategy.onAuthentication(authResult, request, response);}catch (InternalAuthenticationServiceException failed) {logger.error("An internal error occurred while trying to authenticate the user.",failed);unsuccessfulAuthentication(request, response, failed);return;}catch (AuthenticationException failed) {// Authentication failedunsuccessfulAuthentication(request, response, failed);return;}// 如果允许验证继续其他认真。if (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//认证成功,回调successfulAuthentication(request, response, chain, authResult);}

真实验证由子类实现。

 public abstract Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException, IOException,ServletException;

验证成功或失败回调

protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response, FilterChain chain, Authentication authResult)throws IOException, ServletException {if (logger.isDebugEnabled()) {logger.debug("Authentication success. Updating SecurityContextHolder to contain: "+ authResult);}
//设置验证结果SecurityContextHolder.getContext().setAuthentication(authResult);
//记住我rememberMeServices.loginSuccess(request, response, authResult);// Fire eventif (this.eventPublisher != null) {eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}
//successHandler回调successHandler.onAuthenticationSuccess(request, response, authResult);}protected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response, AuthenticationException failed)throws IOException, ServletException {SecurityContextHolder.clearContext();if (logger.isDebugEnabled()) {logger.debug("Authentication request failed: " + failed.toString(), failed);logger.debug("Updated SecurityContextHolder to contain null Authentication");logger.debug("Delegating to authentication failure handler " + failureHandler);}rememberMeServices.loginFail(request, response);failureHandler.onAuthenticationFailure(request, response, failed);}

attemptAuthentication

public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {
//必须是POST方法if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}
//获取用户名和密码String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();
//封装请求UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);
//调用AuthenticationManager()验证return this.getAuthenticationManager().authenticate(authRequest);}

AnonymousAuthenticationFilter

AnonymousAuthenticationFilter过滤器是在UsernamePasswordAuthenticationFilter等过滤器之后,如果它前面的过滤器都没有认证成功,Spring Security则为当前的SecurityContextHolder中添加一个Authenticaiton 的匿名实现类AnonymousAuthenticationToken;

 public AnonymousAuthenticationFilter(String key) {this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));}

doFilter方法,直接触发原始chain过滤。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {if (SecurityContextHolder.getContext().getAuthentication() == null) {SecurityContextHolder.getContext().setAuthentication(createAuthentication((HttpServletRequest) req));
... ... }else {... ... }}chain.doFilter(req, res);}//设置匿名token
protected Authentication createAuthentication(HttpServletRequest request) {AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,principal, authorities);auth.setDetails(authenticationDetailsSource.buildDetails(request));return auth;}

ExceptionTranslationFilter

ExceptionTranslationFilter 异常处理过滤器,该过滤器用来处理在系统认证授权过程中抛出的异常(也就是下一个过滤器FilterSecurityInterceptor),主要是 处理 AuthenticationException 和 AccessDeniedException 

FilterSecurityInterceptor

此过滤器为认证授权过滤器链中最后一个过滤器,该过滤器之后就是请求真正的处理逻辑。

 public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);invoke(fi);}public void invoke(FilterInvocation fi) throws IOException, ServletException {if ((fi.getRequest() != null)&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)&& observeOncePerRequest) {//已经处理。//直接调用真实服务。fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// 第一次调用,处理checking。if (fi.getRequest() != null && observeOncePerRequest) {//设置为true。fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}//前置处理InterceptorStatusToken token = super.beforeInvocation(fi);try {//真实业务逻辑。fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}finally {super.finallyInvocation(token);}//后置处理。super.afterInvocation(token, null);}}

AbstractSecurityInterceptor

 protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private ApplicationEventPublisher eventPublisher;private AccessDecisionManager accessDecisionManager;private AfterInvocationManager afterInvocationManager;private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();private RunAsManager runAsManager = new NullRunAsManager();

beforeInvocation

//object:请求的url。
protected InterceptorStatusToken beforeInvocation(Object object) {Assert.notNull(object, "Object was null");final boolean debug = logger.isDebugEnabled();if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {throw new IllegalArgumentException("Security invocation attempted for object "+ object.getClass().getName()+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "+ getSecureObjectClass());}
//使用当前的访问资源路径去匹配自定义的匹配规则。Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
//如果没有,则返回null。if (attributes == null || attributes.isEmpty()) {if (rejectPublicInvocations) {throw new IllegalArgumentException("Secure object invocation "+ object+ " was denied as public invocations are not allowed via this interceptor. "+ "This indicates a configuration error because the "+ "rejectPublicInvocations property is set to 'true'");}if (debug) {logger.debug("Public object - authentication not attempted");}publishEvent(new PublicInvocationEvent(object));return null; // no further work post-invocation}if (debug) {logger.debug("Secure object: " + object + "; Attributes: " + attributes);}
//没有验证if (SecurityContextHolder.getContext().getAuthentication() == null) {credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"),object, attributes);}
//验证过的信息。Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry {
//鉴权。this.accessDecisionManager.decide(authenticated, object, attributes);}catch (AccessDeniedException accessDeniedException) {publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));throw accessDeniedException;}if (debug) {logger.debug("Authorization successful");}if (publishAuthorizationSuccess) {publishEvent(new AuthorizedEvent(object, attributes, authenticated));}// Attempt to run as a different userAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);if (runAs == null) {if (debug) {logger.debug("RunAsManager did not change Authentication object");}// no further work post-invocationreturn new InterceptorStatusToken(SecurityContextHolder.getContext(), false,attributes, object);}else {if (debug) {logger.debug("Switching to RunAs Authentication: " + runAs);}SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);// need to revert to token.Authenticated post-invocationreturn new InterceptorStatusToken(origCtx, true, attributes, object);}}
 protected void finallyInvocation(InterceptorStatusToken token) {if (token != null && token.isContextHolderRefreshRequired()) {if (logger.isDebugEnabled()) {logger.debug("Reverting to original Authentication: "+ token.getSecurityContext().getAuthentication());}SecurityContextHolder.setContext(token.getSecurityContext());}}

AccessDecisionManager

AccessDecisionManager 用于鉴权。

public interface AccessDecisionManager {void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,InsufficientAuthenticationException;boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);
}

AbstractAccessDecisionManager

public abstract class AbstractAccessDecisionManager implements AccessDecisionManager,InitializingBean, MessageSourceAware {private List<AccessDecisionVoter<?>> decisionVoters;protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();private boolean allowIfAllAbstainDecisions = false;
}
    public boolean supports(ConfigAttribute attribute) {for (AccessDecisionVoter voter : this.decisionVoters) {if (voter.supports(attribute)) {return true;}}return false;}public boolean supports(Class<?> clazz) {for (AccessDecisionVoter voter : this.decisionVoters) {if (!voter.supports(clazz)) {return false;}}return true;}

AccessDecisionVoter

投票。即权限判断。

public interface AccessDecisionVoter<S> {//投票结果:int ACCESS_GRANTED = 1;//通过。int ACCESS_ABSTAIN = 0;//弃权。int ACCESS_DENIED = -1;//反对boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);int vote(Authentication authentication, S object,          Collection<ConfigAttribute> attributes);
}

WebExpressionVoter

WebExpressionVoter ,它能理解怎样解析 SpEL 表达式。 WebExpressionVoter 借助于WebSecurityExpressionHandler 接口的一个实现类来达到这个目的。

public int vote(Authentication authentication, FilterInvocation fi,Collection<ConfigAttribute> attributes) {assert authentication != null;assert fi != null;assert attributes != null;WebExpressionConfigAttribute weca = findConfigAttribute(attributes);if (weca == null) {return ACCESS_ABSTAIN;}EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,fi);ctx = weca.postProcess(ctx, fi);return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED: ACCESS_DENIED;}

RoleVoter

基于角色的鉴权,权限以ROLE_开头。

AuthenticatedVoter

根据认证结果鉴权。RememberMe会使用到。

    public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";

AffirmativeBased

AccessDecisionManager的默认实现。只要有一个通过则通过。没有反对的也通过。

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);if (logger.isDebugEnabled()) {logger.debug("Voter: " + voter + ", returned: " + result);}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"));}// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}

ConsensusBased

同意大于反对,或者同意等于反对(如果允许)则通过。

UnanimousBased

只要有一个不通过,就拒绝。

密码验证时序图

Spring Security源码解析(一)——认证和鉴权相关推荐

  1. Spring全家桶-Spring Security之自定义数据库表认证和鉴权

    Spring全家桶-Spring Security之自定义数据库表认证和鉴权 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供 ...

  2. Spring Security源码解析(二)——引入

    目录 Spring Security的引入 AuthenticationConfiguration WebSecurityConfiguration 引入 FilterChain. 设置FilterC ...

  3. Spring Security实战--(五)认证和鉴权过程

    前面几篇直接讲了Demo,但是可能还是有点混乱,这里再将整个认证过程梳理一下,加深对前面的理解 一.标准的身份验证方案 对一个系统来说,标准的安全身份验证方案应该按如下步骤: 用户使用用户名和密码登录 ...

  4. Spring Security源码解析(四)—— 过滤器

    目录 FilterChainProxy 属性 构造函数 执行Filter 获取Filter VirtualFilterChain 默认过滤器 默认Filter WebAsyncManagerInteg ...

  5. Spring Security源码解析(三)—— HttpSecurity

    目录 SecurityConfigurerAdapter AbstractHttpConfigurer AnonymousConfigurer AbstractAuthenticationFilter ...

  6. 【若依】开源框架学习笔记 07 - 登录认证流程(Spring Security 源码)

    文章目录 一.概述 二.登录过程代码实现 三.用户验证流程(Spring Security 源码) 1.处理用户认证逻辑过滤器 `UsernamePasswordAuthenticationFilte ...

  7. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  8. spring boot 源码解析23-actuate使用及EndPoint解析

    前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...

  9. Spring AOP源码解析-拦截器链的执行过程

    一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...

最新文章

  1. 实战SSM_O2O商铺_38【商品类别】解除商品与商品类别的关联
  2. sas中的sql(2) 行选择 、限制重复、条件运算符、运行前语法检查、feedback、count...
  3. 清华大学人工智能研究院知识智能研究中心成立
  4. 2更新字段其中一位_NoSQL之MongoDB——数据更新操作
  5. hive 运行命令后FAILED: Execution Error, return code 137 from org.apache.hadoop.hive.ql.ex ec.mr.MapredLoc
  6. 「十二省联考 2019」皮配——dp
  7. C# WPF:把文件给我拖进来!!!
  8. [HDU]1723Distribute Message
  9. vue入门:(class与style绑定)
  10. 在命令行英雄的浏览器大战中,JavaScript令人惊讶地崛起
  11. 【学术分享】论文投稿被拒2次,再找第三家的时候突然想放弃怎么办?
  12. 信息系统项目管理师项目合同管理
  13. 讲解NPN与PNP三极管做开关管使用方法与计算
  14. c语言编程入门教程+网易,人话讲编程·C语言入门:第一讲,Hello World
  15. NLP 学习教程 第一节 简介
  16. 【JZOJ 5426】【NOIP2017提高A组集训10.25】摘Galo
  17. ZYNQ-定时器中断使用
  18. float与定位脱离文档流布局规则
  19. Latex基础命令入门
  20. hpp文件和h文件有什么区别

热门文章

  1. ASP.NET中常用功能代码总结(3)——上传图片到数据库
  2. 大话设计模式—中介者模式
  3. 养殖者运送猫狗过河问题(面向对象)
  4. 在html页面提交值到动态页面时出现中文值为乱码的解决方案
  5. 玩点创意编程,发现另一个世界
  6. 手风琴特效这么飒,能用 JavaScript 实现吗?
  7. 别再说找不到Python练手项目了,这80个拿去过冬
  8. centos php7.0 mysql_CentOS 7.3 下 安装LNMP(Nginx1.10+MySQL5.7+PHP7.0.20)
  9. php集合与数组的区别,java集合与数组的区别
  10. lisp语言画阿基米德线_孩子总说“我不会画”!那么孩子是否要先学画形象?...