问题描述
我正在使用 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()
将返回一个 List
castable"到 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());
注意:sessionRegistry
是 SessionRegistryImpl
类的自动装配实现.
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 的在线用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!