I am trying to set up an authentication system on my spring boot application using spring boot security but I am having trouble getting it to work with my database.
If I try to log in with the credentials of a user created in my InMemoryUserDetailsManager, my AuthenticationManager works completely (it throws an error if the credentials are wrong).
However, if I try with the credentials of a user in my database, it doesn't work because it seems to not be logged in with my AuthenticationManager.
I'm new to spring boot and spring security, sorry if my problem is a bit stupid.
Here is my code that might help you understand my problem:
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
return new InMemoryUserDetailsManager(
User.withUsername("user1").password(passwordEncoder.encode("1234")).authorities("USER").build(),
User.withUsername("user2").password(passwordEncoder.encode("1234")).authorities("USER").build(),
User.withUsername("admin").password(passwordEncoder.encode("1234")).authorities("USER","ADMIN").build()
);
}
#Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService){
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setPasswordEncoder(passwordEncoder);
authProvider.setUserDetailsService(userDetailsService);
return new ProviderManager(authProvider);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(csrf ->csrf.disable())
.authorizeHttpRequests(auth->auth.requestMatchers("/auth/**").permitAll())
.authorizeHttpRequests(auth ->
auth.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.httpBasic(Customizer.withDefaults())
.build();
}
#PostMapping("/login")
public Map<String,String> Login(#RequestBody User user){
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword())
);
My database is correctly configured and contains a user
For someone that could be in the same situation as me, u must add this in your code :
#Bean
public UserDetailsService userDetailsService(){
return email->userRepository.findByEmail(email).orElseThrow(()->new UsernameNotFoundException("User not found"));
}
Moreover, u must change your authenticationManager like this :
#Bean
public AuthenticationManager authenticationManager(){
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setPasswordEncoder(passwordEncoder);
authProvider.setUserDetailsService(userDetailsService());
return new ProviderManager(authProvider);
}
Related
Here my configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Bean
public JwtDecoder reactiveJwtDecoder() throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec("JAC1O17W1F3QB9E8B4B1MT6QKYOQB36V".getBytes(), mac.getAlgorithm());
return NimbusJwtDecoder.withSecretKey(secretKey)
.macAlgorithm(MacAlgorithm.HS256)
.build();
}
#Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http
) throws Exception {
Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>> oauth2Customizer = (config) -> config.jwt();
return http
.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.anonymous().disable()
.logout().disable()
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/actuator/**").permitAll()
.antMatchers("/gicar/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2Customizer)
.build();
}
}
Everything works fine.
I need to allow method execution according to jwt token information.
I know that in order to get that, I need to transform the jwt token to an Authentication object.
From my configuration on, what should I add to it, in order to generate an Authentication object and translate claims to authorities?
What I need to do is something like this:
#RestController
#RequestMapping(value = "/qdcf")
#RequiredArgsConstructor
#Timed
public class QdCFController {
private final UsuariRepository usuariRepository;
#GetMapping("/user")
#PreAuthorize("hasRole(ADMIN)")
public Optional<Usuari> user() {
return this.usuariRepository.findOne(UsuariSpecs.hasCodi("11111111A"));
}
}
Your configuration is already enough for Spring to create an Authentication object, the question is how it's going to get roles from your JWT.
To "guide" Spring and your JwtDecoder you should create and configure a bean of JwtAuthenticationConverter type, e.g. like this:
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
final JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// choose a JWT claim name where authorities are stored on login
// authorities claim name defaults to "scope" and "scp" if this method is not used
grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
// here choose a scope prefix that was used
// prefix defaults to "SCOPE_" if this method is not used
grantedAuthoritiesConverter.setAuthorityPrefix("");
final JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
This configuration will help JwtDecoder to translate any authority in the "roles" claim of JWT to GrantedAuthority with no prefix.
I am using Spring Security 5.5.3 to hit two separate SAML Identity Providers (IDP) (similar to, say, Google and Facebook, but not OAuth).
So, in my Security configuration, I define the two RelyingPartyRegistration beans:
public RelyingPartyRegistration site1RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("site1")
// rest of configuration
.build();
return registration;
}
public RelyingPartyRegistration site2RelyingPartyRegistration() {
RelyingPartyRegistration registration = RelyingPartyRegistration.withRegistrationId("site2")
// rest of configuration
.build();
return registration;
}
#Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() throws Exception {
Collection<RelyingPartyRegistration> registrations = Collections.unmodifiableList(
Arrays.asList(
site1RelyingPartyRegistration(),
site2elyingPartyRegistration())
);
InMemoryRelyingPartyRegistrationRepository repository = new InMemoryRelyingPartyRegistrationRepository(registrations);
return repository;
}
Then I define my SuccessHandler:
public class MySuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
// need to determine whether the success came from site1 or site2
}
}
My question is, since I can authenticate from more than one Identity Provider, how can I find out which site it was? The only idea I have is to pull the entire <samlp:Response> from the Saml2AuthenticationToken and parse it to an XML document to get a single String attribute.
Is there an easier way?
In Spring Security 5.6, it's included in the Authentication:
Saml2AuthenticatedPrincipal principal =
(Saml2AuthenticatedPrincipal) authentication.getPrincipal();
String registrationId = principal.getRelyingPartyRegistrationId();
Before Spring Security 5.6, you can customize the Saml2AuthenticatedPrincipal in the authentication provider:
OpenSaml4AuthenticationProvider provider =
new OpenSaml4AuthenticationProvider();
provider.setResponseAuthenticationConverter((params) -> {
Response response = params.getResponse();
Saml2AuthenticationToken token = params.getToken();
RelyingPartyRegistration registration = token.getRelyingPartyRegistration();
String registrationId = registration.getRegistrationId();
// ... create custom authentication that contains the registration id
});
I am developing a RestAPI with SpringBoot and Spring Security. The login end point is POST /session. Can I defer authentication to controller layer like below:
#Configuration(proxyBeanMethods = false)
#EnableWebSecurity(debug = true)
public class AppSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and()
.formLogin().disable()
.httpBasic().disable()
.requestCache().disable()
.logout().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/session").permitAll()
.anyRequest().authenticated();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
And in Controller:
#RestController
#RequestMapping("/session")
public class SessionController {
private final AuthenticationManager authenticationManager;
#Autowired
public SessionController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#PostMapping
public ResponseEntity create(#RequestBody #Validated LoginDTO loginDTO){
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
loginDTO.getUserName().trim(), loginDTO.getPassword());
final Authentication authentication = authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
return ResponseEntity.ok().build();
}
}
Is it bad practice to do authentication like above?
There's nothing inherently wrong with using Spring MVC to handle login. The reason that Spring Security uses a filter is so that it can work in projects that aren't using Spring MVC.
That said, Spring Security's AbstractAuthenticationProcessingFilter does a few other things, too. For example, it protects against Session Fixation by changing the session id, and it changes out the CSRF token assigned to that session. If you have your controller do authentication, you'll likely want it to do those other things, too.
If you don't have a technical need to use Spring MVC, I'd still recommend relying on Spring Security to perform the login so as to not lose those other protections.
Side Note
Regardless of what you do, note that when you override the HttpSecurity method, you don't need to disable httpBasic nor formLogin as those are not turned on by default.
I'm experiencing a really weird issue with spring security.
The remember-me token seems to last for only one automatic login, after that, it stops working.
1. After login:
2. Then, I manually delete the JSESSIONID cookie and reload the page
3. I delete the JSESSIONID cookie again and reload the page again.
Now, I'm logged out!
In the console I get this:
SEVERE [http-nio-8080-exec-10] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [dispatcher] in context with path [] threw exception
org.springframework.security.web.authentication.rememberme.CookieTheftException: Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
I read that this might be the result of the browser issuing multiple requests at the same time, I checked (disabled all the resources, leaving only plain HTML, but to no avail)
Here's my configuration
#EnableWebSecurity
public class Security extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Autowired
DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/assets/**").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.formLogin().permitAll();
http.rememberMe().tokenRepository(persistentTokenRepository()).userDetailsService(customUserDetailsService);
http.logout().permitAll();
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
return tokenRepository;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(customUserDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(11);
}
}
Pulling dataSource from config worked for me, try it
#Autowired
JpaConfiguration jpaConfig;
#Bean(name = "persistentTokenRepository")
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(jpaConfig.dataSource());
return tokenRepository;
}
or you can also try to increase token validity
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/assets/**").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.formLogin().permitAll();
http.rememberMe().tokenRepository(persistentTokenRepository()).userDetailsService(customUserDetailsService)
.tokenValiditySeconds(1209600);
http.logout().permitAll();
}
I am trying to set up spring with websockets and STOMP.
On the client, I send a header variable
'simpSessionId':%session_id%
However, on receiving the message, spring it always places the supplied header in a key called nativeHeaders and a default simpSessionId in the header root.
{simpMessageType=MESSAGE, stompCommand=SEND, nativeHeaders={SPRING.SESSION.ID=[5b1f11d0-ad92-4855-ae44-b2052ecd76d8], Content-Type=[application/json], X-Requested-With=[XMLHttpRequest], simpSessionId=[5b1f11d0-ad92-4855-ae44-b2052ecd76d8], accept-version=[1.2,1.1,1.0], heart-beat=[0,0], destination=[/mobile-server/ping], content-length=[15]}, simpSessionAttributes={}, simpSessionId=1, simpDestination=/mobile-server/ping}
Any ideas how to have spring pick up the supplied session id instead?
Edited
Ok, I have a mobile phone app and a website hitting the same server. I need to be able to set up a webocket on the mobile phone app.
On the mobile phone app, I login to the server through a traditional REST endpoint, and I receive a session-id in the response if successful.
I use webstomp-client on the mobile phone, Spring 4.1.9, Spring Security 4.1, Spring Session 1.2.0.
I would ideally login to the STOMP websocket on the socket CONNECT using a token, but I understand that his is currently impossible because webstomp-client doesn't pass custom headers on CONNECT.
I have two problems:
How do I pass the session id that I retrieve on the REST Login in subsequent requests? I've tried adding headers such as SPRING.SESSION.ID, but stepping through the code I always see the message processing going back to the simpSessionId which is always defaulted to 1, 2 etc. I've tried extending the AbstractSessionWebsocketMessageBrokerConfigurer, but it doesn't pick up my session id, it always looks in the simpSessionAttributes, which is always empty.
The code also seems to try to get the http session, which is a web browser scenario. I'm assuming I should just ignore this
Sessions expire. What should be the strategy for a session that may have expired? Shouldn't I pass a remember-me style authentication token as well? Or should I rely on some everlasting stateless session? This is not clear to me and this aspect seems to be undocumented.
Obviously, I'm doing something very wrong. Here's my config:
#Configuration
#EnableRedisHttpSession(maxInactiveIntervalInSeconds=1200)
public class SessionConfig {
#Inject
ContentNegotiationManager contentNegotiationManager;
#Bean
public RedisConnectionFactory redisConnectionFactory(
#Value("${spring.redis.host}") String host,
#Value("${spring.redis.password}") String password,
#Value("${spring.redis.port}") Integer port) {
JedisConnectionFactory redis = new JedisConnectionFactory();
redis.setUsePool(true);
redis.setHostName(host);
redis.setPort(port);
redis.setPassword(password);
redis.afterPropertiesSet();
return redis;
}
#Bean
public RedisTemplate<String,ExpiringSession> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, ExpiringSession> template = new RedisTemplate<String, ExpiringSession>();
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
#Bean
public <S extends ExpiringSession>SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter(SessionRepository<S> sessionRepository) {
return new SessionRepositoryFilter<S>(sessionRepository);
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean
public HttpSessionStrategy httpSessionStrategy(){
return new SmartSessionStrategy();
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setUseSecureCookie(true);
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
return serializer;
}
}
===
public class SessionWebApplicationInitializer extends AbstractHttpSessionApplicationInitializer {
public SessionWebApplicationInitializer() {
}
public SessionWebApplicationInitializer(Class<?>... configurationClasses) {
super(configurationClasses);
}
#Override
protected void beforeSessionRepositoryFilter(ServletContext servletContext) {
Dynamic registration = servletContext.addFilter("openSessionInViewFilter", new OpenSessionInViewFilter());
if (registration == null) {
throw new IllegalStateException(
"Duplicate Filter registration for openSessionInViewFilter. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(false);
EnumSet<DispatcherType> dispatcherTypes = getSessionDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, false,"/*");
}
}
==
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebsocketMessageBrokerConfigurer<S>{
#Inject
SessionRepository<S> sessionRepository;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/mobile-server");
config.setUserDestinationPrefix("/mobile-user");
}
#Override
public void configureStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/ws")
.setHandshakeHandler(new SessionHandShakeHandler(new TomcatRequestUpgradeStrategy()))
.setAllowedOrigins("*")
.withSockJS()
.setSessionCookieNeeded(false)
;
}
#Override
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(512 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(40000);
}
#Bean
public WebSocketConnectHandler<S> webSocketConnectHandler(SimpMessageSendingOperations messagingTemplate, UsorManager userMgr) {
return new WebSocketConnectHandler<S>(messagingTemplate, userMgr);
}
#Bean
public WebSocketDisconnectHandler<S> webSocketDisconnectHandler(SimpMessageSendingOperations messagingTemplate, WebSocketManager repository) {
return new WebSocketDisconnectHandler<S>(messagingTemplate, repository);
}
}
====
#Configuration
public class WebSocketSecurity extends AbstractSecurityWebSocketMessageBrokerConfigurer{
ApplicationContext context = null;
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().permitAll()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/mobile-server/ping").authenticated()
.simpDestMatchers("/mobile-server/csrf").authenticated()
.simpDestMatchers("/mobile-server/**").hasRole("ENDUSER")
.simpSubscribeDestMatchers("/user/**", "/topic/**").hasRole("ENDUSER")
.anyMessage().denyAll();
}
}
===
I have removed some additional security configurations I have here for brevity sake.
#Configuration
#EnableWebSecurity
#Order(100)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String REMEMBER_ME_COOKIE = "SPRING_SECURITY_REMEMBER_ME_COOKIE";
#Inject
FilterInvocationSecurityMetadataSource securityMetadataSource;
#Inject
SessionRepositoryFilter<? extends ExpiringSession> sessionRepositoryFilter;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setSaltSource(saltSource);
provider.setUserDetailsService(userMgr);
provider.setPasswordEncoder(passwordEncoder);
provider.setMessageSource(messages);
auth.authenticationProvider(provider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenProcessingFilter authenticationTokenProcessingFilter() throws Exception{
return new AuthenticationTokenProcessingFilter(authenticationManagerBean());
}
#Bean
public FilterSecurityInterceptor myFilterSecurityInterceptor(
AuthenticationManager authenticationManager,
AccessDecisionManager accessDecisionManager,
FilterInvocationSecurityMetadataSource metadataSource){
FilterSecurityInterceptor interceptor = new FilterSecurityInterceptor();
interceptor.setAuthenticationManager(authenticationManager);
interceptor.setAccessDecisionManager(accessDecisionManager);
interceptor.setSecurityMetadataSource(securityMetadataSource);
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean
public AccessDecisionManager accessDecisionManager(SiteConfig siteConfig){
URLBasedSecurityExpressionHandler expressionHandler = new URLBasedSecurityExpressionHandler();
expressionHandler.setSiteConfig(siteConfig);
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
webExpressionVoter.setExpressionHandler(expressionHandler);
return new AffirmativeBased(Lists.newArrayList(
webExpressionVoter,
new RoleVoter(),
new AuthenticatedVoter()
));
}
public PasswordFixingAuthenticationProvider customAuthenticationProvider(PasswordEncoder passwordEncoder, SaltSource saltSource){
PasswordFixingAuthenticationProvider provider = new PasswordFixingAuthenticationProvider();
provider.setUserDetailsService(userMgr);
provider.setPasswordEncoder(passwordEncoder);
provider.setSaltSource(saltSource);
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(sessionRepositoryFilter, ChannelProcessingFilter.class)
.antMatcher("/ws/**")
.exceptionHandling()
.accessDeniedPage("/mobile/403")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/ws").permitAll()
.antMatchers("/ws/websocket").permitAll()
.antMatchers("/ws/**").denyAll();
.anyRequest().requiresSecure()
;
}
}
===
public class SmartSessionStrategy implements HttpSessionStrategy {
private HttpSessionStrategy browser;
private HttpSessionStrategy api;
private RequestMatcher browserMatcher = null;
public SmartSessionStrategy(){
this.browser = new CookieHttpSessionStrategy();
HeaderHttpSessionStrategy headerSessionStrategy = new HeaderHttpSessionStrategy();
headerSessionStrategy.setHeaderName(CustomSessionRepositoryMessageInterceptor.SPRING_SESSION_ID_ATTR_NAME);
this.api = headerSessionStrategy;
}
#Override
public String getRequestedSessionId(HttpServletRequest request) {
return getStrategy(request).getRequestedSessionId(request);
}
#Override
public void onNewSession(Session session, HttpServletRequest request, HttpServletResponse response) {
getStrategy(request).onNewSession(session, request, response);
}
#Override
public void onInvalidateSession(HttpServletRequest request, HttpServletResponse response) {
getStrategy(request).onInvalidateSession(request, response);
}
private HttpSessionStrategy getStrategy(HttpServletRequest request) {
if(this.browserMatcher != null)
return this.browserMatcher.matches(request) ? this.browser : this.api;
return SecurityRequestUtils.isApiRequest(request) ? this.api : this.browser;
}
}
I think the question is based on invalid expectations to begin with. You cannot pass the session id and it's not meant to be passed in. You cannot login at the STOMP protocol level, it's not how it it's designed to work.
Although the STOMP protocol does allow for user credentials to be passed in the CONNECT frame that's more useful with STOMP over TCP. In an HTTP scenario we already have authentication and authorization mechanisms in place to rely on. By the time you get to the STOMP CONNECT, you would have had to pass authentication and authorization for the WebSocket handshake URL.
I would start with the Spring reference documentation on Authentication for STOMP/WebSocket messaging if you haven't read that already:
When a WebSocket handshake is made and a new WebSocket session is
created, Spring’s WebSocket support automatically propagates the
java.security.Principal from the HTTP request to the WebSocket
session. After that every message flowing through the application on
that WebSocket session is enriched with the user information. It’s
present in the message as a header.
In other words authentication is the same as for existing web applications. The URL at which the WebSocket endpoint is exposed is just another HTTP endpoint of the application. The same way all other HTTP endpoints are secured is the way the WebSocket handshake is secured. Just like for other HTTP endpoints you don't pass the session id. Instead you're within an existing HTTP session maintained through a cookie.
The handshake cannot be established unless Spring Security authenticates and authorizes the HTTP URL first. From there the STOMP session will pick up the authenticated user and Spring Security offers further ways to authorize individual STOMP messages.
That should all work seamlessly. There is no need to login via STOMP or to pass the Spring Session id at any time.