Spring webSocket - Reject topic subscription based on user rights with IE8 - determineUser() never called - spring

I would like to reject subscription according to user permissions. I found the following solution and it helped for browsers that support websocket:
https://github.com/rstoyanchev/spring-websocket-portfolio/issues/23
the solution is extending DefaultHandshakeHandler and overriding determineUser method.
the issue is - this method is never called when connecting from IE8.
Would appreciate any help. thanks,

It sounds like your question is more about how to associate a user with the session. By default the WebSocket session gets the user from the HttpServletRequest during the handshake (i.e. getPrincipal). The DefaultHandshakeHandler does provide a protected method but that only works for the WebSocket transport. So currently the best way to associate a user with the WebSocket session is to to wrap the HttpServletRequest (e.g. in a Filter) and return the user.

Spring Security 4 adds support for WebSocket message and subscription protection. You can configure it with Java config:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configure(MessageSecurityMetadataSourceRegistry messages) {
messages
.destinationMatchers("/app/your.topic").hasRole("ADMIN")
.anyMessage().hasRole("USER");
}
}
This will protect both, subscriptions and messages sent to /app/your.topic with the role ROLE_ADMIN. Another solution would be mapping the subscription in your controller and using the #PreAuthorize annotation:
#PreAuthorize("hasRole('ROLE_ADMIN')")
#SubscribeMapping("/your.topic")
public String notifications() {
...
}

Related

Spring Boot End Points are not routing correctly using STOMP

I was running into some issues in terms of getting Postman to correctly send messages over my web-socket via STOMP. I made a new project to do some testing and I am having a hard time understanding why my messages are not being sent over.
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket");
registry.addEndpoint("/gs-guide-websocket").setAllowedOrigins("http://localhost:8080").withSockJS();
}
}
As you can see, I registered two endpoints. The first end point is being used by Postman. To clarify this code actually works, I have tested it with SockJS() as a fallback and it works as expected.
For the Web Socket connection via Postman, I am using ws://localhost:3000/gs-guide-websocket, which it successfully connects to. However, when sending messages via Postman, it appears as if it's lost and is never correctly handled by the MessageMapping method as seen below.
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public MessageResponse send(Message message) throws Exception {
return new MessageResponse(message.getContent());
}
I am confused through where my messages are actually being sent to, if anywhere at all. I have tried reading through Postman documentation to no avail. Is there a mistake somewhere in my code where I am not correctly handling this sort of end point or is this an error on Postman's part?
Link to the guide I am using for testing: https://spring.io/guides/gs/messaging-stomp-websocket/
From countless hours of researching this problem, it seems like Postman does not support the websocket process that STOMP requires.
From a comment via: Springboot websocket testing with Postman
For now, Postman does not support STOMP, only RAW. There is currently not any way to subscribe to a topic.
The problem I was having is that it can connect to the WebSocket without a problem. However, you are unable to send a message to the correct route at this given time.
For testing your WebSockets, I would recommend using JS/TS to perform operations. This is because you are able to subscribe to a topic via Stomp.over(socket).

Can I configure the Keycloak Spring Boot adapter so that it populates the user principal even on requests that don't require authorization?

If I have an endpoint which should be publically available but behave differently if you're logged in, I can't specify it in the security-constraints section of my application configuration (otherwise if I'm not logged in I can't reach the endpoint at all). But without it being listed there, the user principal never gets populated on the request, even if a valid Bearer token is provided in the Authorization header.
Is there a way to get the behaviour I want with the basic Spring Boot adapter? If not, am I likely to have more luck with the Spring Security adapter?
A solution to this is to enable preemptive authentication in the Tomcat context. Assuming you're deploying using the embedded server, you can implement a WebServerFactoryCustomizer as follows:
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
#Component
public class PreemptiveAuthWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory>
{
#Override
public void customize( final TomcatServletWebServerFactory factory )
{
factory.addContextCustomizers( context -> context.setPreemptiveAuthentication( true ) );
}
}
Now if a request has an Authorization header sent, it will always be processed even if the request isn't explicitly behind a path which requires authorization.

Having trouble sending data to my websocket created in Spring-Boot from Flutter

I am attempting to send data through IOWebSocketChannel in Flutter.io to a WebSocket created in Spring-Boot.
In spring-boot I have created the typical WebSocket config and controllers that are dealing with client's manipulation of my servers WebSocket. I will post them below just for reference.
WebSocketConfiguration.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry){
registry.addEndpoint("/websocket")
.setAllowedOrigins("*") // allow us to connect to ws://localhost:8080/websocket with the default Spring port configuration.
.withSockJS(); // allows a client which does not support WebSocket natively mimic a WebSocket over an HTTP connection
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry){ //The configureMessageBroker method sets up a simple (in-memory) message broker for our application
registry.enableSimpleBroker("/topic"); //topic to be routed back to client
registry.setApplicationDestinationPrefixes("/app"); //This configuration allows Spring to understand that any message sent to a WebSocket channel name prefixed with /app should be routed to a #MessageMapping in our application.
}
}
WebSocketController.java
#Controller
public class WebSocketController {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketController.class);
#MessageMapping("/send")
#SendTo("/topic/messages")
public Message send(Message message) {
LOGGER.info(String.format("Received message [%s]", message.toString()));
LocalDateTime timestamp = LocalDateTime.now();
return new Message(message.getFrom(), message.getMessage(), timestamp);
}
}
Now When I try using IOWebSocketChannel I perform the typical protocol of connecting to my configured websocket. Below is the code
final channel = IOWebSocketChannel.connect(
"ws://10.0.2.2:8080/websocket"
);
I have then created a method that is supposed to send data to my websocket so I attempt to connect to that endpoint which you see is created in WebSocketController.java called app/send/. Below is the code:
void _sendMessage() {
IOWebSocketChannel channel = IOWebSocketChannel.connect('ws://10.0.2.2:8080/app/send');
channel.sink.add(
json.encode({
"message": "bars",
})
);
}
Now when I check my Spring-Boot server nothing is logged, however, whenever I hot reload in Flutter Spring Boot and my connection to the websocket times out, tomcat server returns this:
So my question is if anybody has been able to make a breakthrough with sending data through websockets from Flutter into Spring-Boot using IOWebSocketChannel? I am also wondering if anyone has found a way to successfully use a STOMP protocol in Flutter.io? I was using stomp_client as it seemed like it was going to do the trick, however correct if I'm wrong, but flutter was giving me errors saying that there doesn't exist any html files, so I'm assuming that library is only for dart in the web.
Your Spring configuration looks good. But client-side you need some tweaks.
I spent some time to figure this out with the https://pub.dev/packages/stomp package. Use a modified version of the connect function provided here. Make sure to use this custom implementation of the connect function.
Future<StompClient> client = customStomp.connect('ws://10.0.2.2:8080/websocket', ...)
Once connected, according to your configuration, you can then send message on the following destination: /app/send.

Azure Active Directory with Springboot : how to handle expired oauth token?

I have a springboot app which use Microsoft Azure active directory to allow authentication (oauth2).
I have followed the "how to" provided by Microsoft (https://learn.microsoft.com/en-us/java/azure/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory?view=azure-java-stable).
Everything is working well except that I have no ideas on how to handle expired token (after 1 hour) in a way that it will not affect the users.
I know that it is possible to get new token with the refresh token but if I take a look in NimbusAuthorizationCodeTokenResponseClient.java the refresh token is not saved anywhere even if it's available.
I don't find any examples on how to keep this refresh token and how to use it, like if it was supposed to work automatically like the whole process.
Can someone have any experience with this Azure Active directory spring boot module?
I'm using Springboot 2.0.4 with azure spring boot module 2.0.5
Your access_token gets automatically refreshed by the refresh_token.
But when your refresh_token token expires you can still run into the same error. To handle it you can make your refresh_token automatically renew the same time you get a new access_token. Use reuseRefreshTokens(false) in the configuration of AuthorizationServerEndpointsConfigurer at the auth-server code:
Take a look at the refreshAccessToken method in the DefaultTokenServices class:
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue,
TokenRequest tokenRequest) {
// Omitted
if (!reuseRefreshToken) {
tokenStore.removeRefreshToken(refreshToken);
refreshToken = createRefreshToken(authentication);
}
// Omitted
}
You should somehow set the reuseRefreshToken flag to false. You can do that in your AuthorizationServerConfigurerAdapter implementation:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
// Other methods
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.reuseRefreshTokens(false);
}
}

How to reject access in Spring after successful authentication

I'm implementing some REST services in Spring and need to reject, in some cases, a successful login of a user.
I have implemented my UserDetailsService and programmed the loadUserByUsername(String username) method, but, when a user gets correctly authenticated, I need to do another validation and, if it fails, reject the access.
To do so I have implemented a listener to detect correct authentications:
#Component
public class LoginSuccessListener implements ApplicationListener{
#Autowired
LicenseControlService licenseControlService;
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent e){
User user = (User)e.getAuthentication().getPrincipal();
//Here I want to reject the access because of whatever logic
}
I need to be sure to apply the logic which might reject the access when the user gets correctly authorized, that's why I need to put the logic when I receive this event.
Is there any way to do that?
Im posting the solution I have adopted in case someone needs the same behaviour:
What I did was, at the class which implements the UserDetailsService, when returning the User class, instantiate it with a false at the enabled property... that returns a 401 to the requester.
I also implemented a CustomAuthenticationEntryPoint which extends BasicAuthenticationEntryPoint, and with an overriden comence method. That method is called whenever Spring is returning an authentication error, so, in this method, I can query the type of the AuthenticationException and decide and modify the returned status according to it.

Resources