Spring security WebSphere上的Spring安全预验证过滤器示例?

Spring security WebSphere上的Spring安全预验证过滤器示例?,spring-security,websphere,websphere-7,websphere-8,pre-authentication,Spring Security,Websphere,Websphere 7,Websphere 8,Pre Authentication,是否有人拥有WebSphere working的Spring安全示例预验证过滤器(WebSpherePreAuthenticatedProcessingFilter)?关于它的文档很少,我似乎无法确定它。我正在寻找的人谁有它的工作,可能愿意提供您的配置的例子。理想情况下是Spring 3.1和7或8 我有一个适当的配置,它似乎“有点”工作。我可以通过WebSphere进行身份验证,然后在我的应用程序中点击URL,但浏览器会返回以下消息: 错误500:java.lang.RuntimeExcept

是否有人拥有WebSphere working的Spring安全示例预验证过滤器(WebSpherePreAuthenticatedProcessingFilter)?关于它的文档很少,我似乎无法确定它。我正在寻找的人谁有它的工作,可能愿意提供您的配置的例子。理想情况下是Spring 3.1和7或8

我有一个适当的配置,它似乎“有点”工作。我可以通过WebSphere进行身份验证,然后在我的应用程序中点击URL,但浏览器会返回以下消息:

错误500:java.lang.RuntimeException:查找用户组时发生异常

我得到如下异常堆栈跟踪:

java.lang.RuntimeException: Error while invoking method java.lang.reflect.Method.getGroupsForUser([UNAUTHENTICATED])
    at org.springframework.security.web.authentication.preauth.websphere.DefaultWASUsernameAndGroupsExtractor.invokeMethod(DefaultWASUsernameAndGroupsExtractor.java:147) [spring-security-web-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.web.authentication.preauth.websphere.DefaultWASUsernameAndGroupsExtractor.getWebSphereGroups(DefaultWASUsernameAndGroupsExtractor.java:115) [spring-security-web-3.1.3.RELEASE.jar:3.1.3.RELEASE]
    at org.springframework.security.web.authentication.preauth.websphere.DefaultWASUsernameAndGroupsExtractor.getWebSphereGroups(
...

Caused by: java.lang.reflect.InvocationTargetException: null
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:60) ~[na:1.6.0]
...

Caused by: com.ibm.websphere.security.EntryNotFoundException: null
    at com.ibm.ws.wim.registry.util.MembershipBridge.getGroupsForUser(MembershipBridge.java:293) ~[com.ibm.ws.runtime.wim.core.jar:201207200704]
...

[12/28/12 14:05:15:879 CST] 00000055 LocalTranCoor E   WLTC0017E: Resources rolled back due to setRollbackOnly() being called.
[12/28/12 14:05:15:879 CST] 00000055 webapp        E com.ibm.ws.webcontainer.webapp.WebApp logServletError SRVE0293E: [Servlet Error]-[ServletNameNotFound]: java.lang.RuntimeException: Exception occured while looking up groups for user
    at org.springframework.security.web.authentication.preauth.websphere.DefaultWASUsernameAndGroupsExtractor.getWebSphereGroups(DefaultWASUsernameAndGroupsExtractor.java:123)
    at org.springframework.security.web.authentication.preauth.websphere.DefaultWASUsernameAndGroupsExtractor.getWebSphereGroups(DefaultWASUsernameAndGroupsExtractor.java:94)
...
我的web.xml文件如下:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    classpath*:/applicationContext-jcr.xml,
    classpath*:/applicationContext-security.xml
  </param-value>
</context-param>


<filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
  <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>

  <filter>
      <filter-name>filterChainProxy</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

<!--  
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
-->

<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
 -->

  <filter-mapping>
    <filter-name>filterChainProxy</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- ====================================================================== -->
<!-- S E R V L E T S -->
<!-- ====================================================================== -->
<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet>
  <servlet-name>ViewStatusMessages</servlet-name>
  <servlet-class>ch.qos.logback.classic.ViewStatusMessagesServlet</servlet-class>
</servlet>

<!-- ====================================================================== -->
<!-- S E R V L E T    M A P P I N G S -->
<!-- ====================================================================== -->

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/app/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
  <servlet-name>ViewStatusMessages</servlet-name>
  <url-pattern>/lbClassicStatus</url-pattern>
</servlet-mapping>

<!-- ====================================================================== -->
<!-- W E L C O M E    F I L E S -->
<!-- ====================================================================== -->
<welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

<!-- Use if configuring for JNDI Datasource on J2EE Server -->
<resource-ref id="ResourceRef_LDP_Datasource">
  <description>Resource reference to the LDP datasource.</description>
  <!-- DB2 -->

  <res-ref-name>jdbc/ldpdbDS</res-ref-name>

  <!-- MS SQL -->
  <!-- 
  <res-ref-name>jdbc/ldpdbMSSQLDS</res-ref-name>
   -->
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
  <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<!-- 
  security-constraint reference:
  http://s170.codeinspot.com/q/3419721

  OK, I figured this out. The problem is that even though I had J2EE security setup in Websphere and was authenticated, 
  my web.xml contained no security constraints. Because of this, Websphere was not supplying the principal for my requests. 
  This is apparently an intentional feature. If you are not accessing a protected URL, you should not need the pre-authentication information. 
  To overcome this, I added a security constraint to my web.xml, which allowed ALL users to access the resources. 
  Effectively, the resources were not secured, but still - there was a constraint now.

  This tricks the Websphere into filling in the user principal information in the request.
  -->

<security-constraint>
    <web-resource-collection>
        <web-resource-name>All areas</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>*</role-name>
    </auth-constraint>
</security-constraint>
<!-- 
<sec:http use-expressions="true">
    <sec:intercept-url pattern="/**" access="denyAll" />
    <sec:form-login />
</sec:http>
   -->
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
  <sec:filter-chain-map path-type="ant">
    <!-- 
        <sec:filter-chain pattern="/**" filters="sif,webspherePreAuthFilter,logoutFilter,etf,fsi"/>
         -->
    <sec:filter-chain pattern="/**" filters="webspherePreAuthFilter,logoutFilter,etf,fsi"/>
  </sec:filter-chain-map>
</bean>
<!-- 
  <bean id="sif" class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
  <bean id="sif" class="org.springframework.security.web.context.SecurityContextIntegrationFilter"/>
-->
<sec:authentication-manager alias="authenticationManager">
  <sec:authentication-provider ref="preAuthenticatedAuthenticationProvider"/>
</sec:authentication-manager>
<bean id="preAuthenticatedAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
  <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
</bean>
<bean id="preAuthenticatedUserDetailsService" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>
<!-- 
  This AbstractPreAuthenticatedProcessingFilter implementation is based on WebSphere authentication. 
  It will use the WebSphere RunAs user principal name as the pre-authenticated principal.
  -->
<bean id="webspherePreAuthFilter" class="org.springframework.security.web.authentication.preauth.websphere.WebSpherePreAuthenticatedProcessingFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
</bean>
<bean id="preAuthenticatedProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
  <constructor-arg value="/"/>
  <constructor-arg>
    <list>
      <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
    </list>
  </constructor-arg>
</bean>
<!-- 
  This AuthenticationDetailsSource implementation will set the pre-authenticated granted authorities based on the WebSphere 
  groups for the current WebSphere user, mapped using the configured Attributes2GrantedAuthoritiesMapper.

  This AuthenticationDetailsSource implementation, when configured with a MutableGrantedAuthoritiesContainer, 
  will set the pre-authenticated granted authorities based on the WebSphere groups for the current WebSphere user, 
  mapped using the configured Attributes2GrantedAuthoritiesMapper. 

  By default, this class is configured to build instances of the PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails class.
  -->
<bean id="authenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.websphere.WebSpherePreAuthenticatedWebAuthenticationDetailsSource">
  <property name="webSphereGroups2GrantedAuthoritiesMapper" ref="websphereUserGroups2GrantedAuthoritiesMapper"/>
</bean>
<bean id="websphereUserGroups2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
  <property name="convertAttributeToUpperCase" value="true"/>
</bean>
<bean id="webXmlResource" class="org.springframework.web.context.support.ServletContextResource">
  <constructor-arg ref="servletContext"/>
  <constructor-arg value="/WEB-INF/web.xml"/>
</bean>
<bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>
<bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">
  <property name="authenticationEntryPoint" ref="preAuthenticatedProcessingFilterEntryPoint"/>
</bean>
<bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
  <property name="allowIfAllAbstainDecisions" value="false"/>
  <property name="decisionVoters">
    <list>
      <ref bean="roleVoter"/>
    </list>
  </property>
</bean>
<!-- See: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/core-web-filters.html -->
<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
  <property name="securityMetadataSource">
    <sec:filter-security-metadata-source>
      <sec:intercept-url pattern="/**" access="ROLE_LDP_ADMINS"/>
    </sec:filter-security-metadata-source>
  </property>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<!-- 
Simply put, the filter wraps the current httprequest with one that delegates request.isUserInRole() and request.getRemoteUser() to acegi. 
<bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter">
  <property name="wrapperClass" value="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper"/>
</bean>
-->

上下文配置位置
classpath*:/applicationContext-jcr.xml,
classpath*:/applicationContext-security.xml
编码滤波器
org.springframework.web.filter.CharacterEncodingFilter
编码
UTF-8
强制编码
真的
过滤链氧
org.springframework.web.filter.DelegatingFilterProxy
编码滤波器
/*
过滤链氧
/*
org.springframework.web.context.ContextLoaderListener
调度员
org.springframework.web.servlet.DispatcherServlet
1.
查看状态消息
ch.qos.logback.classic.ViewStatusMessagesServlet
调度员
/应用程序/*
查看状态消息
/lbClassicStatus
index.jsp
LDP数据源的资源引用。
jdbc/ldpdbDS
javax.sql.DataSource
容器
可分享
所有区域
/*
*
我的Spring安全上下文XML文件如下所示:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    classpath*:/applicationContext-jcr.xml,
    classpath*:/applicationContext-security.xml
  </param-value>
</context-param>


<filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </init-param>
  <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
  </init-param>
</filter>

  <filter>
      <filter-name>filterChainProxy</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>

<!--  
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
-->

<filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- 
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
 -->

  <filter-mapping>
    <filter-name>filterChainProxy</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!-- ====================================================================== -->
<!-- S E R V L E T S -->
<!-- ====================================================================== -->
<servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet>
  <servlet-name>ViewStatusMessages</servlet-name>
  <servlet-class>ch.qos.logback.classic.ViewStatusMessagesServlet</servlet-class>
</servlet>

<!-- ====================================================================== -->
<!-- S E R V L E T    M A P P I N G S -->
<!-- ====================================================================== -->

<servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/app/*</url-pattern>
</servlet-mapping>

<servlet-mapping>
  <servlet-name>ViewStatusMessages</servlet-name>
  <url-pattern>/lbClassicStatus</url-pattern>
</servlet-mapping>

<!-- ====================================================================== -->
<!-- W E L C O M E    F I L E S -->
<!-- ====================================================================== -->
<welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

<!-- Use if configuring for JNDI Datasource on J2EE Server -->
<resource-ref id="ResourceRef_LDP_Datasource">
  <description>Resource reference to the LDP datasource.</description>
  <!-- DB2 -->

  <res-ref-name>jdbc/ldpdbDS</res-ref-name>

  <!-- MS SQL -->
  <!-- 
  <res-ref-name>jdbc/ldpdbMSSQLDS</res-ref-name>
   -->
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
  <res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<!-- 
  security-constraint reference:
  http://s170.codeinspot.com/q/3419721

  OK, I figured this out. The problem is that even though I had J2EE security setup in Websphere and was authenticated, 
  my web.xml contained no security constraints. Because of this, Websphere was not supplying the principal for my requests. 
  This is apparently an intentional feature. If you are not accessing a protected URL, you should not need the pre-authentication information. 
  To overcome this, I added a security constraint to my web.xml, which allowed ALL users to access the resources. 
  Effectively, the resources were not secured, but still - there was a constraint now.

  This tricks the Websphere into filling in the user principal information in the request.
  -->

<security-constraint>
    <web-resource-collection>
        <web-resource-name>All areas</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>*</role-name>
    </auth-constraint>
</security-constraint>
<!-- 
<sec:http use-expressions="true">
    <sec:intercept-url pattern="/**" access="denyAll" />
    <sec:form-login />
</sec:http>
   -->
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
  <sec:filter-chain-map path-type="ant">
    <!-- 
        <sec:filter-chain pattern="/**" filters="sif,webspherePreAuthFilter,logoutFilter,etf,fsi"/>
         -->
    <sec:filter-chain pattern="/**" filters="webspherePreAuthFilter,logoutFilter,etf,fsi"/>
  </sec:filter-chain-map>
</bean>
<!-- 
  <bean id="sif" class="org.springframework.security.context.HttpSessionContextIntegrationFilter"/>
  <bean id="sif" class="org.springframework.security.web.context.SecurityContextIntegrationFilter"/>
-->
<sec:authentication-manager alias="authenticationManager">
  <sec:authentication-provider ref="preAuthenticatedAuthenticationProvider"/>
</sec:authentication-manager>
<bean id="preAuthenticatedAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
  <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
</bean>
<bean id="preAuthenticatedUserDetailsService" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>
<!-- 
  This AbstractPreAuthenticatedProcessingFilter implementation is based on WebSphere authentication. 
  It will use the WebSphere RunAs user principal name as the pre-authenticated principal.
  -->
<bean id="webspherePreAuthFilter" class="org.springframework.security.web.authentication.preauth.websphere.WebSpherePreAuthenticatedProcessingFilter">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
</bean>
<bean id="preAuthenticatedProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
  <constructor-arg value="/"/>
  <constructor-arg>
    <list>
      <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
    </list>
  </constructor-arg>
</bean>
<!-- 
  This AuthenticationDetailsSource implementation will set the pre-authenticated granted authorities based on the WebSphere 
  groups for the current WebSphere user, mapped using the configured Attributes2GrantedAuthoritiesMapper.

  This AuthenticationDetailsSource implementation, when configured with a MutableGrantedAuthoritiesContainer, 
  will set the pre-authenticated granted authorities based on the WebSphere groups for the current WebSphere user, 
  mapped using the configured Attributes2GrantedAuthoritiesMapper. 

  By default, this class is configured to build instances of the PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails class.
  -->
<bean id="authenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.websphere.WebSpherePreAuthenticatedWebAuthenticationDetailsSource">
  <property name="webSphereGroups2GrantedAuthoritiesMapper" ref="websphereUserGroups2GrantedAuthoritiesMapper"/>
</bean>
<bean id="websphereUserGroups2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
  <property name="convertAttributeToUpperCase" value="true"/>
</bean>
<bean id="webXmlResource" class="org.springframework.web.context.support.ServletContextResource">
  <constructor-arg ref="servletContext"/>
  <constructor-arg value="/WEB-INF/web.xml"/>
</bean>
<bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>
<bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">
  <property name="authenticationEntryPoint" ref="preAuthenticatedProcessingFilterEntryPoint"/>
</bean>
<bean id="httpRequestAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
  <property name="allowIfAllAbstainDecisions" value="false"/>
  <property name="decisionVoters">
    <list>
      <ref bean="roleVoter"/>
    </list>
  </property>
</bean>
<!-- See: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/core-web-filters.html -->
<bean id="fsi" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
  <property name="authenticationManager" ref="authenticationManager"/>
  <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
  <property name="securityMetadataSource">
    <sec:filter-security-metadata-source>
      <sec:intercept-url pattern="/**" access="ROLE_LDP_ADMINS"/>
    </sec:filter-security-metadata-source>
  </property>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
<!-- 
Simply put, the filter wraps the current httprequest with one that delegates request.isUserInRole() and request.getRemoteUser() to acegi. 
<bean id="securityContextHolderAwareRequestFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter">
  <property name="wrapperClass" value="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper"/>
</bean>
-->

我没有有效的配置,但有一条建议

  • 启用Spring安全调试日志记录
  • 尽可能紧密地调试到
    Membershippridge.getGroupsForUser
    ,我感觉传递给它的用户名是空的

根据我的直觉,我怀疑预认证用户的名字没有被传递。我模模糊糊地记得几年前我就有过这个问题——需要挖掘代码。

出于某种原因,WebSphere不需要对具有以下约束定义的资源进行身份验证,并且预身份验证也没有发生:

<auth-constraint>
    <role-name>*</role-name>
</auth-constraint>

*
它在稍后阶段尝试确定未知主体的组时失败

我使用默认用户组保护资源,并将所有用户映射到该组:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>AuthTest</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>     
    <auth-constraint>
        <role-name>User</role-name>
    </auth-constraint>    
</security-constraint>

<security-role>
    <role-name>User</role-name>
</security-role>

AuthTest
/*
使用者
使用者

这是一种解决方法,因为我不想要这个角色并管理它的成员,但这是我目前所拥有的一切。

您的xml块被破坏了:例如,
没有显示在呈现的输出中,并且您根本没有
元素。查看完整的堆栈跟踪,而不仅仅是第一行,将是一件有趣的事情。