Spring Security with session get online users 返回空 [英] Spring Security with session get online users returns empty
问题描述
同样的问题有多个版本,但似乎没有一个能解决这个问题.我想从 Spring Security 获得在线用户.我知道我们需要自动装配 SessionRegistry
并使用它.但是,它仍然不起作用.这是代码.不确定是由于自定义用户名、密码身份验证还是由于自定义密码编码器或其他原因.一切似乎都是正确的.即使获取当前登录用户的数据也能正常工作,但未登录用户列表.
There are multiple versions of the same questions around and none seem to address the issue. I would like to get online users from spring security. I understand we need to Autowire SessionRegistry
and use it. But still, it doesn't work. Here is the code.
Not sure whether it is due to custom Username, password authentication or due to custom password encoder or something else. Everything seems to be correct. Even getting the current logged in user's data works fine but not logged in user list.
@EnableJpaRepositories(basePackageClasses = UsersRepository.class)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SessionSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordencoder;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private UsernamePasswordAuthProvider usernamepasswdauth;
@Bean
SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(usernamepasswdauth).userDetailsService(userDetailsService)
.passwordEncoder(passwordencoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.csrf().disable();
http.authorizeRequests() //
.antMatchers("/ua/*").permitAll() //
.antMatchers("/auth/*").authenticated() //
.and().requestCache() //
.requestCache(new NullRequestCache());
http.httpBasic().disable();
http.formLogin().disable();
http.logout().disable();
http
.sessionManagement()
.maximumSessions(1).sessionRegistry(sessionRegistry());
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
密码升级器.java
@Component
@Primary
public class PasswordUpgrader implements PasswordEncoder { // used to upgrade NTML password hashes to Bcrypt
private final static BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder();
private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
@Autowired
JdbcTemplate jdbc;
public String encode(CharSequence rawPassword) {
byte[] bytes = NtlmPasswordAuthentication.nTOWFv1(rawPassword.toString());
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars).toLowerCase();
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (encodedPassword == null || encodedPassword.length() == 0) {
return false;
}
if (encodedPassword.equals(encode(rawPassword))) {
String sql = "update user_data set password=? where password=?";
jdbc.update(sql, new Object[] { bcrypt.encode(rawPassword), encode(rawPassword) });
return true;
} else {
return bcrypt.matches(rawPassword, encodedPassword);
}
}
}
用户名密码AuthProvider.java
@Component
public class UsernamePasswordAuthProvider implements AuthenticationProvider {
Log logger = LogFactory.getLog(getClass());
@Autowired
private PasswordEncoder passwordencoder;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
Userdata userdata;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
String username = String.valueOf(auth.getPrincipal());
String password = String.valueOf(auth.getCredentials());
UserDetails user = userDetailsService.loadUserByUsername(username);
String encodedpassword = user.getPassword().toString();
logger.info("inside username passwd authentication");
if (encodedpassword != null && password != null && passwordencoder.matches(password, encodedpassword)) {
logger.info("inside username passwd authentication");
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
throw new BadCredentialsException("Username/Password Incorrect");
}
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
UnauthController.java
@RestController
@RequestMapping("/ua")
public class UnauthController {
@Autowired
private UsernamePasswordAuthProvider usernamepasswdauth;
@PostMapping("/login")
public Map<String, Object> login(HttpServletRequest req, @RequestBody Map<String, Object> map) {
Authentication auth = usernamepasswdauth.authenticate(new UsernamePasswordAuthenticationToken(
map.get("username").toString(), map.get("password").toString()));
SecurityContextHolder.getContext().setAuthentication(auth);
map.put("sessionid", session.getId());
return map;
}
}
AuthController.java
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
Userdata user;
@Autowired
SessionRegistry sessionregistry;
Log logger = LogFactory.getLog(getClass());
@GetMapping("/onlineusers")
public List<String> authhello(Authentication authentication) {
logger.debug(user.getEmail()); // prints current logged in user's email.
logger.debug(sessionRegistry.getAllPrincipals());//returns empty
return sessionRegistry.getAllPrincipals().stream()
.filter(u -> !sessionRegistry.getAllSessions(u, false).isEmpty()).map(Object::toString)
.collect(Collectors.toList());
}
}
尝试过的方法:
推荐答案
如果您仔细阅读文档 这里,写得很好(虽然很隐蔽).问题的原因在于身份验证后处理数据的方式.在 spring 安全提供的默认身份验证中,成功身份验证后,控制通过管理会话的过滤器传递.但是,如果您使用自定义身份验证并在成功身份验证后重定向用户,则该过滤器不会起作用,这就是会话注册表中未添加任何会话并返回空列表的原因.
If you read carefully in documentation here, it is well-written(very secluded though). The cause of the problem is in the way data is handled after authentication. In default authentication provided by the spring security, after successful authentication the control is passed through a filter managing sessions. However, if you are using customized authentication and redirecting user after successful authentication that filter doesn't come into the way and that's why no sessions are added in the session registry and it returns empty list.
解决办法是在spring security的session管理配置中设置带有session registry的认证策略.这将导致预期的行为.您会发现代码更有帮助.
The solution is to set authentication strategy with session registry into session management configuration of spring security. This will lead to the the expected behaviour. You'll find the code more helpful.
方法一:
会话的Spring安全配置
Spring security configuration for session
http
.sessionManagement()
.sessionAuthenticationStrategy(concurrentSession())
.maximumSessions(-1)
.expiredSessionStrategy(sessionInformationExpiredStrategy())
定义bean
@Bean
public CompositeSessionAuthenticationStrategy concurrentSession() {
ConcurrentSessionControlAuthenticationStrategy concurrentAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
List<SessionAuthenticationStrategy> delegateStrategies = new ArrayList<SessionAuthenticationStrategy>();
delegateStrategies.add(concurrentAuthenticationStrategy);
delegateStrategies.add(new SessionFixationProtectionStrategy());
delegateStrategies.add(new RegisterSessionAuthenticationStrategy(sessionRegistry()));
return new CompositeSessionAuthenticationStrategy(delegateStrategies);
}
@Bean
SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
return new CustomSessionInformationExpiredStrategy("/login");
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
这是 CustomSessionInformationExpiredStrategy.java
Here's the CustomSessionInformationExpiredStrategy.java
public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
private String expiredUrl = "";
public CustomSessionInformationExpiredStrategy(String expiredUrl) {
this.expiredUrl = expiredUrl;
}
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException, ServletException {
HttpServletRequest request = sessionInformationExpiredEvent.getRequest();
HttpServletResponse response = sessionInformationExpiredEvent.getResponse();
request.getSession();// creates a new session
response.sendRedirect(request.getContextPath() + expiredUrl);
}
}
方法:2
在 spring 安全配置中,使用 方法 1 中的 concurrentSession().
In spring security configuration, use the concurrentSession() from method 1.
http.sessionManagement().sessionAuthenticationStrategy(concurrentSession());
http.addFilterBefore(concurrentSessionFilter(), ConcurrentSessionFilter.class);
这里是 CustomConcurrentSessionFilter.java
Here's CustomConcurrentSessionFilter.java
public class CustomConcurrentSessionFilter extends ConcurrentSessionFilter {
public CustomConcurrentSessionFilter(SessionRegistry sessionRegistry) {
super(sessionRegistry);
}
public CustomConcurrentSessionFilter(SessionRegistry sessionRegistry, SessionInformationExpiredStrategy sessionInformationExpiredStrategy) {
super(sessionRegistry, sessionInformationExpiredStrategy);
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
super.doFilter(req, res, chain);
}
}
还在为某事挠头吗?在 Github repo 中找到工作示例.随时提出问题或贡献.
Still scratching head for something? Find the working example at Github repo. Feel free to raise issues or contribute.
这篇关于Spring Security with session get online users 返回空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!