同样的问题有多个版本,似乎没有一个解决这个问题。我想从Spring Security获得在线用户。我知道我们需要AutowireSessionRegister
并使用它。但是,它仍然不起作用。这是代码。不确定是由于自定义用户名、密码身份验证还是由于自定义密码编码器或其他原因。一切似乎都是正确的。即使获取当前登录用户的数据也可以正常工作,但未登录用户列表。
@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();
}
}
@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);
}
}
}
@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);
}
}
@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;
}
}
@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 security提供的默认身份验证中,在成功的身份验证之后,控制权会通过一个管理会话的过滤器。但是,如果您使用自定义身份验证并在成功身份验证后重定向用户,则该过滤器不会碍事,这就是为什么没有会话添加到会话注册表中,并且它会返回空列表。
解决方案是将带有会话注册表的身份验证策略设置到Spring Security性的会话管理配置中。这将导致预期的行为。你会发现代码更有用。
方法一:
会话的 Spring 安全配置
http
.sessionManagement()
.sessionAuthenticationStrategy(concurrentSession())
.maximumSessions(-1)
.expiredSessionStrategy(sessionInformationExpiredStrategy())
为定义beans
@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();
}
这是CustomSessionInformation ExpiredStrategy.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 Security配置中,使用方法1中的conprestSession()。
http.sessionManagement().sessionAuthenticationStrategy(concurrentSession());
http.addFilterBefore(concurrentSessionFilter(), ConcurrentSessionFilter.class);
以下是 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找到工作示例。请随意提出问题或发表意见。