@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class}) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { boolean debug() default false; }
@EnableWebSecurity是开启spring security的默认行为。
@Import(WebSecurityConfiguration.class) 将WebSecurityConfiguration类放入Spring的IoC容器中,WebSecurityConfiguration完成初始化。
debug参数默认为false,用于指定是否采用调试模式,在调试模式下,每个请求的详细信息和所经过的过滤器,以及调用栈都会被打印到控制台。
该方法中用Spring Security配置文件中继承的WebSecurityConfigurerAdapter的配置类来初始化Security Configurer列表,Security Configurer列表是启用所需的安全策略的依据。WebSecurityConfigurer继承了WebSecurityConfigurerAdapter的配置器。
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor, @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) throws Exception { this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor)); if (this.debugEnabled != null) { this.webSecurity.debug(this.debugEnabled); } ? webSecurityConfigurers.sort(WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE); Integer previousOrder = null; Object previousConfig = null; ? Iterator var5; SecurityConfigurer config; for(var5 = webSecurityConfigurers.iterator(); var5.hasNext(); previousConfig = config) { config = (SecurityConfigurer)var5.next(); Integer order = WebSecurityConfiguration.AnnotationAwareOrderComparator.lookupOrder(config); if (previousOrder != null && previousOrder.equals(order)) { throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order + " was already used on " + previousConfig + ", so it cannot be used on " + config + " too."); } ? previousOrder = order; } ? var5 = webSecurityConfigurers.iterator(); ? while(var5.hasNext()) { config = (SecurityConfigurer)var5.next(); // 将配置的每一个SecurityConfigurer列表传递给WebSecurity this.webSecurity.apply(config); } ? this.webSecurityConfigurers = webSecurityConfigurers; }
springSecurityFilterChain()构造过滤器链
该方法提供了一个名为springSecurityFilterChain的bean,返回一个Filter对象。
@Bean( name = {"springSecurityFilterChain"} ) public Filter springSecurityFilterChain() throws Exception { // 如果没有配置过webSecurityConfigurers则会用WebSecurityConfigurerAdapter中的配置作为默认行为 boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty(); if (!hasConfigurers) { WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() { }); this.webSecurity.apply(adapter); } // 通过调用WebSecurity的build方法生成过滤器 return (Filter)this.webSecurity.build(); } ?
Spring Security的核心是一条过滤器链,Spring Security生效的关键是Web.xml中配置的Spring Security提供的过滤器。DelegatingFilterProxy是Spring提供的Servlet Filter代理,通过下面的配置注入springSecurityFilterChain的bean,并代理这个过滤器。Springboot中自动配置了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>
WebSecurity的build方法最终调用的是doBuild,由WebSecurity继承的AbstractConfiguredSecurityBuilder提供
public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = this.doBuild(); return this.object; } else { throw new AlreadyBuiltException("This object has already been built"); } } protected abstract O doBuild() throws Exception;
doBuild方法中调用了WebSecurity的performBuild方法
protected final O doBuild() throws Exception { synchronized(this.configurers) { // 按照状态一次执行相应的方法 this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING; this.beforeInit(); // 初始化状态,将所有的HttpSecurity添加到WebSecurity中 this.init(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING; this.beforeConfigure(); this.configure(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING; // 在BUILDING阶段调用WebSecurity的performBuild方法 O result = this.performBuild(); this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT; return result; } }
在preformBuild方法中Spring Security完成了所有过滤器的构建,最终返回一个过滤器链代理类filterChainProxy。
protected Filter performBuild() throws Exception { Assert.state(!this.securityFilterChainBuilders.isEmpty(), () -> { return "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly"; }); int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size(); List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize); Iterator var3 = this.ignoredRequests.iterator(); ? while(var3.hasNext()) { RequestMatcher ignoredRequest = (RequestMatcher)var3.next(); securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0])); } ? var3 = this.securityFilterChainBuilders.iterator(); ? while(var3.hasNext()) { SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next(); // 每个HttpSecurity生成一条过滤器链 // HttpSecurity来自于继承了WebSecurityConfigureAdapter的WebSecurityConfigure配置文件 securityFilterChains.add(securityFilterChainBuilder.build()); } // filterChainProxy间接继承了Filter 可以作为过滤器使用 // 携带了若干条过滤器链,将过滤器职责派发到链上的每个过滤器上 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); if (this.httpFirewall != null) { filterChainProxy.setFirewall(this.httpFirewall); } ? filterChainProxy.afterPropertiesSet(); Filter result = filterChainProxy; if (this.debugEnabled) { this.logger.warn("\n\n********************************************************************\n********** Security debugging is enabled. *************\n********** This may include sensitive information. *************\n********** Do not use in a production system! *************\n********************************************************************\n\n"); result = new DebugFilter(filterChainProxy); } ? this.postBuildAction.run(); return (Filter)result; }
filterChainProxy携带了若干条过滤器链,将过滤器职责派发到链上的每个过滤器上。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean clearContext = request.getAttribute(FILTER_APPLIED) == null; if (clearContext) { try { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); // 派发到过滤器链上 this.doFilterInternal(request, response, chain); } finally { SecurityContextHolder.clearContext(); request.removeAttribute(FILTER_APPLIED); } } else { this.doFilterInternal(request, response, chain); }
doFilterInternal是真正执行虚拟过滤器链逻辑的方法。
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // HTTP防火墙 FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request); HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response); // 根据配置的RequestMatcher决定每个请求经过哪些过滤器 List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest); if (filters != null && filters.size() != 0) { // 将所有的过滤器合并成一条虚拟过滤器链 FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters); // 模拟过滤器的执行流程,执行整条过滤器链 vfc.doFilter(fwRequest, fwResponse); } else { 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); } } ?
chain.doFilter(fwRequest, fwResponse) 调用的是VirtualFilterChain类中的方法。
private static class VirtualFilterChain implements FilterChain { private final FilterChain originalChain; private final List<? extends Filter> additionalFilters; private int currentPosition = 0; ? public VirtualFilterChain(FilterChain chain, List<? extends Filter> additionalFilters) { this.originalChain = chain; this.additionalFilters = additionalFilters; } ? public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.currentPosition == this.additionalFilters.size()) { // 执行过滤器链后,调用真实的FilterChain,完成原生过滤器的剩余逻辑 this.originalChain.doFilter(request, response); } else { ++this.currentPosition; Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1); // 通过改变下标回调的方式按照顺序执行每个过滤器 nextFilter.doFilter(request, response, this); } ? } }
FilterComparator维护了一个有序的过滤器注册表filterToOrder
? FilterComparator() { FilterComparator.Step order = new FilterComparator.Step(100, 100); this.put(ChannelProcessingFilter.class, order.next()); this.put(ConcurrentSessionFilter.class, order.next()); this.put(WebAsyncManagerIntegrationFilter.class, order.next()); this.put(SecurityContextPersistenceFilter.class, order.next()); this.put(HeaderWriterFilter.class, order.next()); this.put(CorsFilter.class, order.next()); this.put(CsrfFilter.class, order.next()); this.put(LogoutFilter.class, order.next()); // ...... }
一些主要过滤器的顺序如下图所示:
用于过滤请求使用http协议还是https协议。
private ChannelDecisionManager channelDecisionManager; // 用于判断当前请求与协议是否相符 private FilterInvocationSecurityMetadataSource securityMetadataSource; // 用于储存请求与协议的对应关系
doFilter方法做了两件事,首先找出当前请求需要的协议,attr的值有:
ANY_CHANNEL:http、https
REQUIRES_SECURE_CHANNEL:https
REQUIRES_INSECURE_CHANNEL:http
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; FilterInvocation fi = new FilterInvocation(request, response, chain); // 找出当前请求所需要的协议 Collection<ConfigAttribute> attr = this.securityMetadataSource.getAttributes(fi); if (attr != null) { if (this.logger.isDebugEnabled()) { this.logger.debug("Request: " + fi.toString() + "; ConfigAttributes: " + attr); } // 判断当前请求是否与协议相符 this.channelDecisionManager.decide(fi, attr); if (fi.getResponse().isCommitted()) { return; } } // 跳到下一个过滤器 chain.doFilter(request, response); }
判断当前请求是否与协议相符. 若不相符, 则修改协议并自动跳转。
public void decide(FilterInvocation invocation, Collection<ConfigAttribute> config) throws IOException, ServletException { Iterator var3 = config.iterator(); ? while(var3.hasNext()) { ConfigAttribute attribute = (ConfigAttribute)var3.next(); if ("ANY_CHANNEL".equals(attribute.getAttribute())) { return; } } ? var3 = this.channelProcessors.iterator(); ? while(var3.hasNext()) { ChannelProcessor processor = (ChannelProcessor)var3.next(); processor.decide(invocation, config); if (invocation.getResponse().isCommitted()) { break; } } ? }
用于控制SecurityContext在一次请求中的生命周期,请求来临时,创建SecurityContext安全上下文信息,请求结束时清空SecurityContextHolder。
在WebSecurityConfigure配置文件中通过HttpSecurity.securityContext()引入配置对象SecurityContextConfigure进行配置
用于使用用户名和密码进行身份验证的过滤器,通过HttpSecurity.formLogin进行配置。
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } ? if (password == null) { password = ""; } ? username = username.trim(); // 用户信息的载体类,用来存储及传递用户名和用户密码 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); // 调用getAuthenticationManager()方法获取AuthenticationManager实例 // 调用获取到的AuthenticationManager实例的authenticate()方法对封装在authRequest return this.getAuthenticationManager().authenticate(authRequest); }
当用户没有登录直接访问资源的时候,会从cookie中找不用户信息,如果可以识别出cookie用户则不需要填写用户名和密码直接请求受保护的资源。首先会分析SecurityContext中有没有Authentication对象,如果有则进入下一个过滤器,否则检查request里有没有rememberMe的cookie信息,如果有则解析cookie,判断用户是否具有权限。
通过HttpSecurity调用rememverMe的相关方法进行配置。
.rememberMe() .tokenRepository(persistentTokenRepository()) // 配置token的持久化存储 .tokenValiditySeconds(604800) //一周过期 .userDetailsService(userDetailsService()) //从数据库拿到后使用这个做登录
/* 配置token存到数据库 */ @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); //建表语句 只需要使用一次 用后注释掉 tokenRepository.setCreateTableOnStartup(true); return tokenRepository; }
匿名认证过滤器,Spring Security中所有资源的访问都是有Authentication的,对于不需要通过UsernamePasswordAuthenticatioFilter的资源,会授予匿名用户身份。可以通过HttpSecurity.anonymous()进行配置。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // 如果没有授权 if (SecurityContextHolder.getContext().getAuthentication() == null) { SecurityContextHolder.getContext().setAuthentication(this.createAuthentication((HttpServletRequest)req)); if (this.logger.isDebugEnabled()) { this.logger.debug("Populated SecurityContextHolder with anonymous token: ‘" + SecurityContextHolder.getContext().getAuthentication() + "‘"); } } else if (this.logger.isDebugEnabled()) { this.logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: ‘" + SecurityContextHolder.getContext().getAuthentication() + "‘"); } ? chain.doFilter(req, res); }
protected Authentication createAuthentication(HttpServletRequest request) { AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(this.key, this.principal, this.authorities); auth.setDetails(this.authenticationDetailsSource.buildDetails(request)); return auth; }
实现了Spring Security的异常处理机制
doFilter方法
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); this.logger.debug("Chain processed normally"); } catch (IOException var9) { throw var9; } catch (Exception var10) { Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var10); RuntimeException ase = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); // 判断是否是AuthenticationException异常 不是则为null if (ase == null) { ase = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); } // 判断是否是AccessDeniedException 不是则为null if (ase == null) { if (var10 instanceof ServletException) { throw (ServletException)var10; } ? if (var10 instanceof RuntimeException) { throw (RuntimeException)var10; } ? throw new RuntimeException(var10); } ? if (response.isCommitted()) { throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var10); } // 如果抛出的是AuthenticationException和AccessDeniedException则执行处理异常的方法 this.handleSpringSecurityException(request, response, chain, (RuntimeException)ase); } ? }
handleSpringSecurityException用于处理异常
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { // 如果是AuthenticationException则send出去 if (exception instanceof AuthenticationException) { this.logger.debug("Authentication exception occurred; redirecting to authentication entry point", exception); this.sendStartAuthentication(request, response, chain, (AuthenticationException)exception); // 如果是AccessDeniedException异常且拿到的是匿名Token或记住我Token则send出去 } else if (exception instanceof AccessDeniedException) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (!this.authenticationTrustResolver.isAnonymous(authentication) && !this.authenticationTrustResolver.isRememberMe(authentication)) { this.logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", exception); // 否则 AccessDeniedHandler 用来处理授权异常 执行handle方法 this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception); } else { this.logger.debug("Access is denied (user is " + (this.authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", exception); this.sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication", "Full authentication is required to access this resource"))); } } ? }
sendStartAuthentication方法
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { SecurityContextHolder.getContext().setAuthentication((Authentication)null); this.requestCache.saveRequest(request, response); this.logger.debug("Calling Authentication entry point."); // AuthenticationEntryPoint 这个用来处理认证异常 this.authenticationEntryPoint.commence(request, response, reason); }
认证异常的处理逻辑——commence方法
当发生认证异常的时候,进行重定向,重定向到登录页面。
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { String redirectUrl = null; if (this.useForward) { if (this.forceHttps && "http".equals(request.getScheme())) { redirectUrl = this.buildHttpsRedirectUrlForRequest(request); } ? if (redirectUrl == null) { String loginForm = this.determineUrlToUseForThisRequest(request, response, authException); if (logger.isDebugEnabled()) { logger.debug("Server side forward to: " + loginForm); } ? RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm); dispatcher.forward(request, response); return; } } else { redirectUrl = this.buildRedirectUrlToLoginPage(request, response, authException); } ? this.redirectStrategy.sendRedirect(request, response, redirectUrl); }
授权异常的处理逻辑——handle方法
服务端跳转返回403
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { if (!response.isCommitted()) { if (this.errorPage != null) { request.setAttribute("SPRING_SECURITY_403_EXCEPTION", accessDeniedException); response.setStatus(HttpStatus.FORBIDDEN.value()); RequestDispatcher dispatcher = request.getRequestDispatcher(this.errorPage); dispatcher.forward(request, response); } else { response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase()); } } ? }
用于决定访问特定路径应该具备的权限,访问的用户的角色,权限是什么,访问的路径需要什么角色和权限。
doFilter()方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 将请求上下文封装为一个FilterInvocation FilterInvocation fi = new FilterInvocation(request, response, chain); // 调用该FilterInvocation执行安全认证 this.invoke(fi); }
invoke()方法
public void invoke(FilterInvocation fi) throws IOException, ServletException { // 如果被指定为请求中只执行一次且已经执行过直接放行进入下一个过滤器 if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // 如果被指定为请求中只执行一次但未执行过 设置已执行标志 if (fi.getRequest() != null && this.observeOncePerRequest) { fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE); } // 进行安全检查(认证+授权)如果存在异常则进行异常处理 不继续后续的过滤器 InterceptorStatusToken token = super.beforeInvocation(fi); ? try { // 继续执行后续的过滤器 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.finallyInvocation(token); } ? super.afterInvocation(token, (Object)null); } ? }
beforeInvocation()方法
protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); boolean debug = this.logger.isDebugEnabled(); if (!this.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: " + this.getSecureObjectClass()); } else { // 从安全配置中获取安全元数据,记录在 attributes Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); if (attributes != null && !attributes.isEmpty()) { if (debug) { this.logger.debug("Secure object: " + object + "; Attributes: " + attributes); } ? if (SecurityContextHolder.getContext().getAuthentication() == null) { this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } // 如果安全认证token存在,则检查是否需要认证,如果需要,则执行认证并更行 // 安全上下文中的安全认证token,如果认证失败,抛出异常 AuthenticationException Authentication authenticated = this.authenticateIfRequired(); ? try { // 现在已经确保用户通过了认证,现在基于登录的当前用户信息和目标资源的安全配置属性 // 进行相应的权限检查,如果检查失败,则抛出相应的异常 AccessDeniedException this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException var7) { this.publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, var7)); throw var7; } ? if (debug) { this.logger.debug("Authorization successful"); } ? if (this.publishAuthorizationSuccess) { this.publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } ? Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs == null) { if (debug) { this.logger.debug("RunAsManager did not change Authentication object"); } ? return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); } else { if (debug) { this.logger.debug("Switching to RunAs Authentication: " + runAs); } ? SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); return new InterceptorStatusToken(origCtx, true, attributes, object); } // attributes==null说明该安全对象没有配置安全控制,可以被公开访问 // 如果系统配置了拒绝公开调用,则抛出异常拒绝当前请求 } else if (this.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‘"); // 系统允许公开调用,则不做安全检查 返回null } else { if (debug) { this.logger.debug("Public object - authentication not attempted"); } ? this.publishEvent(new PublicInvocationEvent(object)); return null; } } }
【Spring Security】2. EableWebSecurity、WebSecurityConfiguration和过滤器链源码解析
原文:https://www.cnblogs.com/xdcat/p/13472011.html