首页 文章资讯内容详情

Spring Security 架构与源码分析

2026-06-01 4 花语

本文内容纲要:

-核心对象 -SecurityContextHolder,SecurityContext和Authentication -UserDetails与UserDetailsService -GrantedAuthority -小结 -Authentication认证 -AuthenticationManager -AuthenticationProvider -定制AuthenticationManagers -授权与访问控制 -websecurity如何实现 -参考

SpringSecurity主要实现了Authentication(认证,解决whoareyou?)和AccessControl(访问控制,也就是whatareyouallowedtodo?,也称为Authorization)。SpringSecurity在架构上将认证与授权分离,并提供了扩展点。

核心对象

主要代码在spring-security-core包下面。要了解SpringSecurity,需要先关注里面的核心对象。

SecurityContextHolder,SecurityContext和Authentication

SecurityContextHolder是SecurityContext的存放容器,默认使用ThreadLocal存储,意味SecurityContext在相同线程中的方法都可用。

SecurityContext主要是存储应用的principal信息,在SpringSecurity中用Authentication来表示。

获取principal:

Objectprincipal=SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if(principalinstanceofUserDetails){ Stringusername=((UserDetails)principal).getUsername(); }else{ Stringusername=principal.toString(); }

在SpringSecurity中,可以看一下Authentication定义:

publicinterfaceAuthenticationextendsPrincipal,Serializable{ Collection<?extendsGrantedAuthority>getAuthorities(); /** *通常是密码 */ ObjectgetCredentials(); /** *Storesadditionaldetailsabouttheauthenticationrequest.ThesemightbeanIP *address,certificateserialnumberetc. */ ObjectgetDetails(); /** *用来标识是否已认证,如果使用用户名和密码登录,通常是用户名 */ ObjectgetPrincipal(); /** *是否已认证 */ booleanisAuthenticated(); voidsetAuthenticated(booleanisAuthenticated)throwsIllegalArgumentException; }

在实际应用中,通常使用UsernamePasswordAuthenticationToken:

publicabstractclassAbstractAuthenticationTokenimplementsAuthentication, CredentialsContainer{ } publicclassUsernamePasswordAuthenticationTokenextendsAbstractAuthenticationToken{ }

一个常见的认证过程通常是这样的,创建一个UsernamePasswordAuthenticationToken,然后交给authenticationManager认证(后面详细说明),认证通过则通过SecurityContextHolder存放Authentication信息。

UsernamePasswordAuthenticationTokenauthenticationToken= newUsernamePasswordAuthenticationToken(loginVM.getUsername(),loginVM.getPassword()); Authenticationauthentication=this.authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication);

UserDetails与UserDetailsService

UserDetails是SpringSecurity里的一个关键接口,他用来表示一个principal。

publicinterfaceUserDetailsextendsSerializable{ /** *用户的授权信息,可以理解为角色 */ Collection<?extendsGrantedAuthority>getAuthorities(); /** *用户密码 * *@returnthepassword */ StringgetPassword(); /** *用户名 * */ StringgetUsername(); booleanisAccountNonExpired(); booleanisAccountNonLocked(); booleanisCredentialsNonExpired(); booleanisEnabled(); }

UserDetails提供了认证所需的必要信息,在实际使用里,可以自己实现UserDetails,并增加额外的信息,比如email、mobile等信息。

在Authentication中的principal通常是用户名,我们可以通过UserDetailsService来通过principal获取UserDetails:

publicinterfaceUserDetailsService{ UserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException; }

GrantedAuthority

在UserDetails里说了,GrantedAuthority可以理解为角色,例如ROLE_ADMINISTRATORorROLE_HR_SUPERVISOR。

小结

SecurityContextHolder,用来访问SecurityContext. SecurityContext,用来存储Authentication. Authentication,代表凭证. GrantedAuthority,代表权限. UserDetails,用户信息. UserDetailsService,获取用户信息.

Authentication认证

AuthenticationManager

实现认证主要是通过AuthenticationManager接口,它只包含了一个方法:

publicinterfaceAuthenticationManager{ Authenticationauthenticate(Authenticationauthentication) throwsAuthenticationException; }

authenticate()方法主要做三件事:

如果验证通过,返回Authentication(通常带上authenticated=true)。 认证失败抛出AuthenticationException 如果无法确定,则返回null

AuthenticationException是运行时异常,它通常由应用程序按通用方式处理,用户代码通常不用特意被捕获和处理这个异常。

AuthenticationManager的默认实现是ProviderManager,它委托一组AuthenticationProvider实例来实现认证。

AuthenticationProvider和AuthenticationManager类似,都包含authenticate,但它有一个额外的方法supports,以允许查询调用方是否支持给定Authentication类型: publicinterfaceAuthenticationProvider{ Authenticationauthenticate(Authenticationauthentication) throwsAuthenticationException; booleansupports(Class<?>authentication); }

ProviderManager包含一组AuthenticationProvider,执行authenticate时,遍历Providers,然后调用supports,如果支持,则执行遍历当前provider的authenticate方法,如果一个provider认证成功,则break。

publicAuthenticationauthenticate(Authenticationauthentication) throwsAuthenticationException{ Class<?extendsAuthentication>toTest=authentication.getClass(); AuthenticationExceptionlastException=null; Authenticationresult=null; booleandebug=logger.isDebugEnabled(); for(AuthenticationProviderprovider:getProviders()){ if(!provider.supports(toTest)){ continue; } if(debug){ logger.debug("Authenticationattemptusing" +provider.getClass().getName()); } try{ result=provider.authenticate(authentication); if(result!=null){ copyDetails(authentication,result); break; } } catch(AccountStatusExceptione){ prepareException(e,authentication); //SEC-546:Avoidpollingadditionalprovidersifauthfailureisdueto //invalidaccountstatus throwe; } catch(InternalAuthenticationServiceExceptione){ prepareException(e,authentication); throwe; } catch(AuthenticationExceptione){ lastException=e; } } if(result==null&&parent!=null){ //Allowtheparenttotry. try{ result=parent.authenticate(authentication); } catch(ProviderNotFoundExceptione){ //ignoreaswewillthrowbelowifnootherexceptionoccurredpriorto //callingparentandtheparent //maythrowProviderNotFoundeventhoughaproviderinthechildalready //handledtherequest } catch(AuthenticationExceptione){ lastException=e; } } if(result!=null){ if(eraseCredentialsAfterAuthentication &&(resultinstanceofCredentialsContainer)){ //Authenticationiscomplete.Removecredentialsandothersecretdata //fromauthentication ((CredentialsContainer)result).eraseCredentials(); } eventPublisher.publishAuthenticationSuccess(result); returnresult; } //Parentwasnull,ordidntauthenticate(orthrowanexception). if(lastException==null){ lastException=newProviderNotFoundException(messages.getMessage( "ProviderManager.providerNotFound", newObject[]{toTest.getName()}, "NoAuthenticationProviderfoundfor{0}")); } prepareException(lastException,authentication); throwlastException; }

从上面的代码可以看出,ProviderManager有一个可选parent,如果parent不为空,则调用parent.authenticate(authentication)

AuthenticationProvider

AuthenticationProvider有多种实现,大家最关注的通常是DaoAuthenticationProvider,继承于AbstractUserDetailsAuthenticationProvider,核心是通过UserDetails来实现认证,DaoAuthenticationProvider默认会自动加载,不用手动配。

先来看AbstractUserDetailsAuthenticationProvider,看最核心的authenticate:

publicAuthenticationauthenticate(Authenticationauthentication) throwsAuthenticationException{ //必须是UsernamePasswordAuthenticationToken Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "OnlyUsernamePasswordAuthenticationTokenissupported")); //获取用户名 Stringusername=(authentication.getPrincipal()==null)?"NONE_PROVIDED" :authentication.getName(); booleancacheWasUsed=true; //从缓存获取 UserDetailsuser=this.userCache.getUserFromCache(username); if(user==null){ cacheWasUsed=false; try{ //retrieveUser抽象方法,获取用户 user=retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); } catch(UsernameNotFoundExceptionnotFound){ logger.debug("User"+username+"notfound"); if(hideUserNotFoundExceptions){ thrownewBadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Badcredentials")); } else{ thrownotFound; } } Assert.notNull(user, "retrieveUserreturnednull-aviolationoftheinterfacecontract"); } try{ //预先检查,DefaultPreAuthenticationChecks,检查用户是否被lock或者账号是否可用 preAuthenticationChecks.check(user); //抽象方法,自定义检验 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } catch(AuthenticationExceptionexception){ if(cacheWasUsed){ //Therewasaproblem,sotryagainafterchecking //wereusinglatestdata(i.e.notfromthecache) cacheWasUsed=false; user=retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); preAuthenticationChecks.check(user); additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); } else{ throwexception; } } //后置检查DefaultPostAuthenticationChecks,检查isCredentialsNonExpired postAuthenticationChecks.check(user); if(!cacheWasUsed){ this.userCache.putUserInCache(user); } ObjectprincipalToReturn=user; if(forcePrincipalAsString){ principalToReturn=user.getUsername(); } returncreateSuccessAuthentication(principalToReturn,authentication,user); }

上面的检验主要基于UserDetails实现,其中获取用户和检验逻辑由具体的类去实现,默认实现是DaoAuthenticationProvider,这个类的核心是让开发者提供UserDetailsService来获取UserDetails以及PasswordEncoder来检验密码是否有效:

privateUserDetailsServiceuserDetailsService; privatePasswordEncoderpasswordEncoder;

看具体的实现,retrieveUser,直接调用userDetailsService获取用户:

protectedfinalUserDetailsretrieveUser(Stringusername, UsernamePasswordAuthenticationTokenauthentication) throwsAuthenticationException{ UserDetailsloadedUser; try{ loadedUser=this.getUserDetailsService().loadUserByUsername(username); } catch(UsernameNotFoundExceptionnotFound){ if(authentication.getCredentials()!=null){ StringpresentedPassword=authentication.getCredentials().toString(); passwordEncoder.isPasswordValid(userNotFoundEncodedPassword, presentedPassword,null); } thrownotFound; } catch(ExceptionrepositoryProblem){ thrownewInternalAuthenticationServiceException( repositoryProblem.getMessage(),repositoryProblem); } if(loadedUser==null){ thrownewInternalAuthenticationServiceException( "UserDetailsServicereturnednull,whichisaninterfacecontractviolation"); } returnloadedUser; }

再来看验证:

protectedvoidadditionalAuthenticationChecks(UserDetailsuserDetails, UsernamePasswordAuthenticationTokenauthentication) throwsAuthenticationException{ Objectsalt=null; if(this.saltSource!=null){ salt=this.saltSource.getSalt(userDetails); } if(authentication.getCredentials()==null){ logger.debug("Authenticationfailed:nocredentialsprovided"); thrownewBadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Badcredentials")); } //获取用户密码 StringpresentedPassword=authentication.getCredentials().toString(); //比较passwordEncoder后的密码是否和userdetails的密码一致 if(!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword,salt)){ logger.debug("Authenticationfailed:passworddoesnotmatchstoredvalue"); thrownewBadCredentialsException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Badcredentials")); } }

小结:要自定义认证,使用DaoAuthenticationProvider,只需要为其提供PasswordEncoder和UserDetailsService就可以了。

定制AuthenticationManagers

SpringSecurity提供了一个Builder类AuthenticationManagerBuilder,借助它可以快速实现自定义认证。

看官方源码说明:

SecurityBuilderusedtocreateanAuthenticationManager.Allowsforeasilybuildinginmemoryauthentication,LDAPauthentication,JDBCbasedauthentication,addingUserDetailsService,andaddingAuthenticationProviders.

AuthenticationManagerBuilder可以用来Build一个AuthenticationManager,可以创建基于内存的认证、LDAP认证、JDBC认证,以及添加UserDetailsService和AuthenticationProvider。

简单使用:

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true) publicclassApplicationSecurityextendsWebSecurityConfigurerAdapter{ publicSecurityConfiguration(AuthenticationManagerBuilderauthenticationManagerBuilder,UserDetailsServiceuserDetailsService,TokenProvidertokenProvider,CorsFiltercorsFilter,SecurityProblemSupportproblemSupport){ this.authenticationManagerBuilder=authenticationManagerBuilder; this.userDetailsService=userDetailsService; this.tokenProvider=tokenProvider; this.corsFilter=corsFilter; this.problemSupport=problemSupport; } @PostConstruct publicvoidinit(){ try{ authenticationManagerBuilder .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); }catch(Exceptione){ thrownewBeanInitializationException("Securityconfigurationfailed",e); } } @Override protectedvoidconfigure(HttpSecurityhttp)throwsException{ http .addFilterBefore(corsFilter,UsernamePasswordAuthenticationFilter.class) .exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) .and() .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/register").permitAll() .antMatchers("/api/activate").permitAll() .antMatchers("/api/authenticate").permitAll() .antMatchers("/api/account/reset-password/init").permitAll() .antMatchers("/api/account/reset-password/finish").permitAll() .antMatchers("/api/profile-info").permitAll() .antMatchers("/api/**").authenticated() .antMatchers("/management/health").permitAll() .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN) .antMatchers("/v2/api-docs/**").permitAll() .antMatchers("/swagger-resources/configuration/ui").permitAll() .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN) .and() .apply(securityConfigurerAdapter()); } }

授权与访问控制

一旦认证成功,我们可以继续进行授权,授权是通过AccessDecisionManager来实现的。框架有三种实现,默认是AffirmativeBased,通过AccessDecisionVoter决策,有点像ProviderManager委托给AuthenticationProviders来认证。

publicvoiddecide(Authenticationauthentication,Objectobject, Collection<ConfigAttribute>configAttributes)throwsAccessDeniedException{ intdeny=0; //遍历DecisionVoter for(AccessDecisionVotervoter:getDecisionVoters()){ //投票 intresult=voter.vote(authentication,object,configAttributes); if(logger.isDebugEnabled()){ logger.debug("Voter:"+voter+",returned:"+result); } switch(result){ caseAccessDecisionVoter.ACCESS_GRANTED: return; caseAccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } //一票否决 if(deny>0){ thrownewAccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied","Accessisdenied")); } //Togetthisfar,everyAccessDecisionVoterabstained checkAllowIfAllAbstainDecisions(); }

来看AccessDecisionVoter:

booleansupports(ConfigAttributeattribute); booleansupports(Class<?>clazz); intvote(Authenticationauthentication,Sobject, Collection<ConfigAttribute>attributes);

object是用户要访问的资源,ConfigAttribute则是访问object要满足的条件,通常payload是字符串,比如ROLE_ADMIN。所以我们来看下RoleVoter的实现,其核心就是从authentication提取出GrantedAuthority,然后和ConfigAttribute比较是否满足条件。

publicbooleansupports(ConfigAttributeattribute){ if((attribute.getAttribute()!=null) &&attribute.getAttribute().startsWith(getRolePrefix())){ returntrue; } else{ returnfalse; } } publicbooleansupports(Class<?>clazz){ returntrue; } publicintvote(Authenticationauthentication,Objectobject, Collection<ConfigAttribute>attributes){ if(authentication==null){ returnACCESS_DENIED; } intresult=ACCESS_ABSTAIN; //获取GrantedAuthority信息 Collection<?extendsGrantedAuthority>authorities=extractAuthorities(authentication); for(ConfigAttributeattribute:attributes){ if(this.supports(attribute)){ //默认拒绝访问 result=ACCESS_DENIED; //Attempttofindamatchinggrantedauthority for(GrantedAuthorityauthority:authorities){ //判断是否有匹配的authority if(attribute.getAttribute().equals(authority.getAuthority())){ //可访问 returnACCESS_GRANTED; } } } } returnresult; }

这里要疑问,ConfigAttribute哪来的?其实就是上面ApplicationSecurity的configure里的。

websecurity如何实现

Web层中的SpringSecurity(用于UI和HTTP后端)基于ServletFilters,下图显示了单个HTTP请求的处理程序的典型分层。

SpringSecurity通过FilterChainProxy作为单一的Filter注册到web层,Proxy内部的Filter。

FilterChainProxy相当于一个filter的容器,通过VirtualFilterChain来依次调用各个内部filter

publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse, FilterChainchain)throwsIOException,ServletException{ booleanclearContext=request.getAttribute(FILTER_APPLIED)==null; if(clearContext){ try{ request.setAttribute(FILTER_APPLIED,Boolean.TRUE); doFilterInternal(request,response,chain); } finally{ SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } else{ doFilterInternal(request,response,chain); } } privatevoiddoFilterInternal(ServletRequestrequest,ServletResponseresponse, FilterChainchain)throwsIOException,ServletException{ FirewalledRequestfwRequest=firewall .getFirewalledRequest((HttpServletRequest)request); HttpServletResponsefwResponse=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?"hasnomatchingfilters" :"hasanemptyfilterlist")); } fwRequest.reset(); chain.doFilter(fwRequest,fwResponse); return; } VirtualFilterChainvfc=newVirtualFilterChain(fwRequest,chain,filters); vfc.doFilter(fwRequest,fwResponse); } privatestaticclassVirtualFilterChainimplementsFilterChain{ privatefinalFilterChainoriginalChain; privatefinalList<Filter>additionalFilters; privatefinalFirewalledRequestfirewalledRequest; privatefinalintsize; privateintcurrentPosition=0; privateVirtualFilterChain(FirewalledRequestfirewalledRequest, FilterChainchain,List<Filter>additionalFilters){ this.originalChain=chain; this.additionalFilters=additionalFilters; this.size=additionalFilters.size(); this.firewalledRequest=firewalledRequest; } publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse) throwsIOException,ServletException{ if(currentPosition==size){ if(logger.isDebugEnabled()){ logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) +"reachedendofadditionalfilterchain;proceedingwithoriginalchain"); } //Deactivatepathstrippingasweexitthesecurityfilterchain this.firewalledRequest.reset(); originalChain.doFilter(request,response); } else{ currentPosition++; FilternextFilter=additionalFilters.get(currentPosition-1); if(logger.isDebugEnabled()){ logger.debug(UrlUtils.buildRequestUrl(firewalledRequest) +"atposition"+currentPosition+"of"+size +"inadditionalfilterchain;firingFilter:" +nextFilter.getClass().getSimpleName()+""); } nextFilter.doFilter(request,response,this); } } }

参考

https://spring.io/guides/topicals/spring-security-architecture/ https://docs.spring.io/spring-security/site/docs/5.0.5.RELEASE/reference/htmlsingle/#overall-architecture

作者:Jadepeng

出处:jqpeng的技术记事本--http://www.cnblogs.com/xiaoqi

您的支持是对博主最大的鼓励,感谢您的认真阅读。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

本文内容总结:核心对象,SecurityContextHolder,SecurityContext和Authentication,UserDetails与UserDetailsService,GrantedAuthority,小结,Authentication认证,AuthenticationManager,AuthenticationProvider,定制AuthenticationManagers,授权与访问控制,websecurity如何实现,参考,

原文链接:https://www.cnblogs.com/xiaoqi/p/spring-security.html