本文介绍了使用 Spring Security 的在线用户的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Spring Security,我想知道当前有哪些用户在线.我首先尝试使用 SessionRegistryImpl<session-management session-authentication-strategy-ref="..." .../> 的方法,但我猜这个列表存储在内存中,我想避免它(这将是一个巨大的网站,很多用户将同时在线,列表可能会变得很大).如果我错了,请纠正我.

I'm using Spring Security and I would like to know which users are currently online. I first tried the approach using SessionRegistryImpl and <session-management session-authentication-strategy-ref="..." ... />, but I guess this List is stored in memory and I would like to avoid it (it's going to be a huge website and a lot of users will be online at the same time, the List can become huge). Please correct me if I'm wrong.

我尝试的第二种方法是使用侦听器和 HttpSessionListener 接口和自定义 AuthenticationManager 并将在线标记"存储在数据库中.基本上,该标志在我的身份验证管理器的 authenticate(...) 方法中设置为 true 并在我的 sessionDestroyed(...) 方法中设置为 false会话监听器.

The second approach I tried is using a listener and the HttpSessionListener interface and a custom AuthenticationManager and storing the "is online flag" in the database. Basically, the flag is set to true in the authenticate(...) method of my authentication manager and set to false in the sessionDestroyed(...) method of my session listener.

web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <display-name>Test</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/security.xml</param-value>
    </context-param>

    <!-- Security -->
    <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>

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

    <listener>
        <listener-class>my.package.SessionListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
</web-app>

Spring 安全配置:

Spring Security configuration:

<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    <beans:bean id="authenticationManager" class="my.package.security.AuthenticationManager" />

    <http disable-url-rewriting="true" authentication-manager-ref="authenticationManager">
        <!--<intercept-url pattern="/" access="ROLE_ANONYMOUS" />-->
        <intercept-url pattern="/login*" access="ROLE_ANONYMOUS" />
        <intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" />
        <intercept-url pattern="/*" access="ROLE_USER" />
        <form-login login-processing-url="/authorize" login-page="/login" authentication-failure-url="/login-failed" />
        <logout logout-url="/logout" logout-success-url="/login" />
        <remember-me data-source-ref="dataSource" />
    </http>
</beans:beans>

my.package.SessionListener:

my.package.SessionListener:

public class SessionListener implements HttpSessionListener
{
    public void sessionCreated(HttpSessionEvent httpSessionEvent)
    {

    }

    public void sessionDestroyed(HttpSessionEvent httpSessionEvent)
    {
        UserJpaDao userDao = WebApplicationContextUtils.getWebApplicationContext(httpSessionEvent.getSession().getServletContext()).getBean(UserJpaDao.class);

        Authentication a = SecurityContextHolder.getContext().getAuthentication();
        if(a != null)
        {
            User loggedInUser = userDao.findByAlias(a.getName());

            if(loggedInUser != null)
            {
                loggedInUser.setOnline(false);
                userDao.save(loggedInUser);
            }
        }
    }
}

my.package.security.AuthenticationManager:

my.package.security.AuthenticationManager:

public class AuthenticationManager implements org.springframework.security.authentication.AuthenticationManager
{
    @Autowired
    UserJpaDao userDao;

    public Authentication authenticate(Authentication authentication) throws AuthenticationException
    {
        User loggedInUser = null;
        Collection<? extends GrantedAuthority> grantedAuthorities = null;

        ...

        loggedInUser = userDao.findByAlias(authentication.getName());
        if(loggedInUser != null)
        {
            // Verify password etc.
            loggedInUser.setOnline(true);
            userDao.save(loggedInUser);
        }
        else
        {
            loggedInUser = null;
            throw new BadCredentialsException("Unknown username");
        }

        return new UsernamePasswordAuthenticationToken(loggedInUser, authentication.getCredentials(), grantedAuthorities);
    }
}

更新:几乎一切都运行良好.唯一的问题是,当会话因超时而过期时,SecurityContextHolder.getContext().getAuthentication()sessionDestroyed(...) 方法中返回 null.手动触发注销时效果很好.

Update: almost everything is working perfectly. The only problem is that when the session expires due to timeout, SecurityContextHolder.getContext().getAuthentication() returns null in the sessionDestroyed(...) method. It works perfectly when the logout is manually triggered.

有人可以帮我吗?非常感谢任何提示.

Can someone help me? Any hint is greatly appreciated.

谢谢

推荐答案

我决定采用会话注册表方法(只是因为我无法使其他方法起作用).这是我的代码(重要部分).

I decided to undertake the session registry method (only because I was not able to make the other method work). Here is my code (important parts).

web.xml:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
         http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <display-name>Test</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/applicationContext.xml
            /WEB-INF/security.xml
        </param-value>
    </context-param>

    ...

    <!-- Security -->
    <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>

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

    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <session-config>
        <session-timeout>1</session-timeout>
    </session-config>
</web-app>

security.xml:

security.xml:

<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    <beans:bean id="authenticationManager" class="my.package.security.AuthenticationManager" />
    <beans:bean id="userDetailsDao" class="my.package.dao.UserDetailsDao" />

    <http disable-url-rewriting="true" authentication-manager-ref="authenticationManager">
        <intercept-url pattern="/login*" access="ROLE_ANONYMOUS" />
        <intercept-url pattern="/favicon.ico" access="ROLE_ANONYMOUS" />
        <intercept-url pattern="/*" access="ROLE_USER" />
        <form-login login-processing-url="/authorize" login-page="/login" authentication-failure-url="/login-failed" />
        <logout logout-url="/logout" logout-success-url="/login" />
        <remember-me data-source-ref="dataSource" user-service-ref="userDetailsDao" />
        <session-management session-authentication-strategy-ref="sas" invalid-session-url="/invalid-session" />
    </http>

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

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

my.package.security.AuthenticationManager:

my.package.security.AuthenticationManager:

public class AuthenticationManager implements org.springframework.security.authentication.AuthenticationManager
{
    @Autowired
    UserJpaDao userDao;

    public Authentication authenticate(Authentication authentication) throws AuthenticationException
    {
        UserDetails userDetails = null;

        if(authentication.getPrincipal() == null || authentication.getCredentials() == null)
        {
            throw new BadCredentialsException("Invalid username/password");
        }

        User loggedInUser = userDao.findByAlias(authentication.getName());
        if(loggedInUser != null)
        {
            // TODO: check credentials
            userDetails = new UserDetails(loggedInUser);
        }
        else
        {
            loggedInUser = null;
            throw new BadCredentialsException("Unknown username");
        }

        return new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());
    }
}

my.package.dao.UserDetailsDao(仅需要记住我的功能):

my.package.dao.UserDetailsDao (this is needed only for the remember-me functionality):

public class UserDetailsDao implements UserDetailsService
{
    @Autowired
    UserJpaDao userDao;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        User user = userDao.findByAlias(username);
        if(user != null)
        {
            return new UserDetails(user);
        }

        throw new UsernameNotFoundException("The specified user cannot be found");
    }
}

my.package.UserDetails:

my.package.UserDetails:

public class UserDetails implements org.springframework.security.core.userdetails.UserDetails
{
    private String alias;
    private String encryptedPassword;

    public UserDetails(User user)
    {
        this.alias = user.getAlias();
        this.encryptedPassword = user.getEncryptedPassword();
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return authorities;
    }

    @Override
    public String getPassword()
    {
        return this.encryptedPassword;
    }

    @Override
    public String getUsername()
    {
        return this.alias;
    }

    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    @Override
    public boolean isEnabled()
    {
        return true;
    }
}

sessionRegistry.getAllPrincipals() 将返回一个 Listcastable"到 List.

sessionRegistry.getAllPrincipals() will return a List<Object> "castable" to List<UserDetails>.

我的获取在线用户列表的代码,其中类User的对象通过JPA持久化到数据库中:

My code to get a list of online users, where the objects of type class User are persisted in the database through JPA:

List<User> onlineUsers = userDao.findByListOfUserDetails((List<UserDetails>)(List<?>)sessionRegistry.getAllPrincipals());

注意:sessionRegistrySessionRegistryImpl 类的自动装配实现.

Note: sessionRegistry is an Autowired implementation of the class SessionRegistryImpl.

注意:对于记住我的功能,我使用的是持久令牌方法.数据库中需要一个 persistent_logins(参见 10.3 持久令牌方法).

Note: for the remember-me functionality, I'm using the persistent token approach. A persistent_logins is needed in the database (see 10.3 Persistent Token Approach).

希望这对其他人有用.

这篇关于使用 Spring Security 的在线用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-22 11:37