【问题标题】:Spring SessionRegistryImpl not getting correct sessionIdSpring SessionRegistryImpl 没有得到正确的 sessionId
【发布时间】:2014-08-28 12:36:26
【问题描述】:

我正在尝试创建访问日志。到目前为止,我可以使用此类成功记录会话的开始:

public class CustomAuthenticationProcessingFilter extends J2eePreAuthenticatedProcessingFilter {

    @Autowired
    AccessLogService accessLogService;

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

        if (authentication.isAuthenticated()) {

            String authenticatedUser = authentication.getName();
            WebAuthenticationDetails wad = (WebAuthenticationDetails) authentication.getDetails();

            accessLogService.addAccessLog("IP: "  + wad.getRemoteAddress(), authenticatedUser, request.getSession().getId());           

        }
        super.successfulAuthentication(request, response, authentication);
    }
}

这很好用。我有这个类用于注销部分:

public class CustomSecurityContextLogoutHandler extends SecurityContextLogoutHandler {

   @Autowired
   private SessionRegistry sessionRegistry;

   @Autowired
   private AccessLogDAO accessLogDAO;

   private boolean deleteLtpaCookies = Boolean.FALSE;

    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

    if (authentication != null && authentication.getPrincipal() != null) {
        List<SessionInformation> sessions = sessionRegistry.getAllSessions(((User) authentication.getPrincipal()), false);
        if (sessions != null) {
            for (SessionInformation sessionInformation : sessions) {
                List<String> sessionsToClose = new ArrayList<String>();
                sessionsToClose.add(sessionInformation.getSessionId());
                accessLogDAO.closeAccessLog(sessionsToClose);

                sessionInformation.expireNow();
            }
        }
    }

}

但是当我这样做时

List<SessionInformation> sessions = sessionRegistry.getAllSessions(((User) authentication.getPrincipal()), false)

sessions 为空。当我调试 SessionRegistryImpl 时,我发现问题出在 getAllSessions 的第一行:

final Set<String> sessionsUsedByPrincipal = principals.get(principal);

问题在于 sessionsUsedByPrincipal 获取的 sessionId 与数据库上存储的任何 sessionId 不同。 任何想法为什么会发生这种情况?我将不胜感激任何 cmets 或帮助。

编辑

答案效果很好,但它记录了两次。 这是我的 applicationContext-security

中更重要的部分
<sec:http auto-config='false' use-expressions="true" authentication-manager-ref="authenticationManager" entry-point-ref="authenticationProcessingFilterEntryPoint">

    <sec:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
    <sec:custom-filter position="PRE_AUTH_FILTER" ref="preAuthFilter"/>
    <sec:custom-filter position="LOGOUT_FILTER" ref="logoutFilter"/>
    <sec:session-management session-authentication-strategy-ref="sas"/>
</sec:http>

<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"/>

<bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
    <property name="sessionRegistry" ref="sessionRegistry" />
    <property name="expiredUrl" value="/login.faces" />
</bean>

<bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />

<bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
    <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
    <property name="maximumSessions" value="-1" />
    <property name="exceptionIfMaximumExceeded" value="true" />
</bean>

<bean id="simpleAttributes2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
    <property name="convertAttributeToUpperCase" value="true"/>
</bean>

<bean id="webXmlMappableAttributesRetriever" class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever"/>

<bean id="j2eeBasedPreAuthenticatedWebAuthenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
    <property name="mappableRolesRetriever" ref="webXmlMappableAttributesRetriever"/>
    <property name="userRoles2GrantedAuthoritiesMapper" ref="simpleAttributes2GrantedAuthoritiesMapper"/>
</bean>

<bean id="preAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationDetailsSource" ref="j2eeBasedPreAuthenticatedWebAuthenticationDetailsSource"/>
</bean>

<bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <constructor-arg value="/login.faces"/>
    <property name="forceHttps" value="false"/>     
</bean>

<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <constructor-arg value="/login.faces"/>
    <constructor-arg>
        <list>
            <bean class="cr.go.nov.proy.web.util.AccessLogHandler"/>
        </list>
    </constructor-arg>
</bean>

不是我写的,也不是完全看懂,只是添加了我需要的东西。也许你可以看看,看看是否有什么可能导致这种行为。感谢您的帮助。

【问题讨论】:

  • Spring Security 已经为你清理了东西,你也不应该扩展SecurityContextLogoutHandler,而是实现你自己的LogoutHandler,它将被添加(作为第一个!)到链中。但是我建议实现一个ApplicationListener 来监听AuthenticationSuccessEvent 以获得成功登录。 WebAuthenticationDetails 还包含使用的会话 ID,如果还没有会话,则不包含会话 ID(您的代码会强制创建会话!)。也不知道你为什么要让会话过期(因为 spring 会处理这个问题)。

标签: java spring session jakarta-ee logging


【解决方案1】:

对于初学者,我建议创建一个ApplicationListener 来侦听AuthenticationSuccessEventInteractiveAuthenticationSuccessEvent 事件以开始记录您的访问日志。

要记录注销,请创建一个专用的LogoutHandler,而不是扩展现有的。

将使用的会话 ID 是 WebAuthenticationDetails 的一部分,所以我也建议使用它而不是解决它。如果还没有会话,您的代码将强制创建会话。

public class AccessLogHandler implements ApplicationListener<AbstractAuthenticationEvent>, LogoutHandler {

    @Autowired
    private SessionRegistry sessionRegistry;

    @Autowired
    private AccessLogService accessLogService;

    public void onApplicationEvent(AbstractAuthenticationEvent event) {
        if (event instanceof AuthenticationSuccessEvent || event instanceof InteractiveAuthenticationSuccessEvent) {
            Authentication authentication = event.getAuthentication();
            WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
            accessLogService.addAccessLog("IP: "  + details.getRemoteAddress(), authentication.getName(), details.getSessionId());           
        }
    }

    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
            WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
            accessLogService.closeAccessLog(Collections.singletonList(details.getSessionId());
            SessionInformation si = sessionRegistry.getSessionInformation(details.getSessionId());
            if (si != null) {
                si.expireNow();
            }
    }
}

这样的东西应该可以工作,你可以删除你的扩展类,留下一个基本的配置。 ApplicationListener 是自动注册的,LogoutHandler 您需要添加到 LogoutFilter 中,恐怕您需要明确配置。但从您已经扩展另一个LogoutHandler 的事实来看,这是您已经在做的事情。

你的配置也是相当冗长的东西,比如登录,会话控制可以用命名空间来配置。以下配置应该与您自己的配置相同(可能更多一些)。

<sec:http auto-config='false' use-expressions="true" authentication-manager-ref="authenticationManager" >
    <sec:custom-filter position="PRE_AUTH_FILTER" ref="preAuthFilter"/>
    <sec:custom-filter position="LOGOUT_FILTER" ref="logoutFilter"/>
    <sec:login-form login-page="/login.faces" />
    <sec:session-management>
        <sec:concurrency-control max-sessions="-1" session-registry-alias="sessionRegistry" error-if-maximum-exceeded="true" expired-url="/login.faces"/>
    </sec:session-management>
</sec:http>

<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"/>

<bean id="j2eeBasedPreAuthenticatedWebAuthenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
    <property name="mappableRolesRetriever">
        <bean class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever"/>
    </property>
    <property name="userRoles2GrantedAuthoritiesMapper">
        <bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
            <property name="convertAttributeToUpperCase" value="true"/>
        </bean>
    </property>
</bean>

<bean id="preAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationDetailsSource" ref="j2eeBasedPreAuthenticatedWebAuthenticationDetailsSource"/>
</bean>

<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <constructor-arg value="/login.faces"/>
    <constructor-arg>
        <list>
            <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            <bean class="cr.go.nov.proy.web.util.AccessLogHandler"/>
        </list>
    </constructor-arg>
</bean>

在您的配置中,您没有注册任何SessionAuthenticationStrategy,这也意味着没有向SessionRegistry 注册任何会话。正如RegisterSessionAuthenticationStrategy 负责处理的那样。注意 - 这假设您使用的是 Spring Security 3.2!

【讨论】:

  • 效果很好,谢谢!但是有一个细节......它记录了两次会话。我没有做 applicationContext-security 我只是添加了我需要的东西,介意看看吗?也许你可以看看是不是有什么原因造成的,我会用文件更新我的问题。再次感谢!
  • 您是否不小心注册了两次课程?或者你是加载 xml 文件两次。如果有 2 个 bean 实例,它将被记录两次。
  • 我不确定你两次注册课程是什么意思...我发现了一个问题,会话没有关闭,我以为是,但不是。显然,在注册新会话时会保留不同的 sessionId,所以我当前的 sessiinId 永远不会被关闭。这就是 SessionRegistryImpl 中的 registerNewSession 函数。我很困惑……
  • SessionRegistry 不用于关闭会话,实际会话正在由您的容器关闭。 SessionRegistry 仅包含有关会话的一些元数据,而不是实际的 HttpSession 本身。
  • 我的意思是注册两次确保您的安全配置只加载一次(仅由ContextLoaderListener else bean 将被复制并且事件将被处理两次。)。
猜你喜欢
  • 1970-01-01
  • 2018-02-19
  • 1970-01-01
  • 2017-01-03
  • 2019-07-20
  • 2020-02-04
  • 2018-11-26
  • 2019-02-18
相关资源
最近更新 更多