Closed mattnicolls closed 5 years ago
so im guessing you are using the spring security (-core) grails plugin.
what you are looking for is likely spring-security-messaging
which ships a SecurityContextChannelInterceptor
that ensures the spring security SecurityContextHolder
is properly populated for incoming messages (allowing to use springSecurityService.currentUser
on the incoming messages' thread).
therefore,
1) add the necessary spring-security dependencies in your build.gradle
dependencies
section
compile "org.springframework.security:spring-security-config"
compile "org.springframework.security:spring-security-messaging"
2) exclude SecurityFilterAutoConfiguration
in your Application
class (it conflicts with grails spring-security-core)
@EnableAutoConfiguration(exclude = [SecurityFilterAutoConfiguration])
class Application extends GrailsAutoConfiguration { ... }
3) add a config class extending AbstractSecurityWebSocketMessageBrokerConfigurer
and register it as a spring bean, e.g. detected by component-scan or registered in grails-app/conf/spring/resources.groovy
@Configuration
class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
/**
* here, you can configure client inbound channel security
* ref. https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/websocket.html
*/
@Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {}
/**
* you may want to disable csrf protection depending on your setup
* ref. https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/websocket.html#websocket-sameorigin
*/
@Override
protected boolean sameOriginDisabled() {
return true
}
}
AbstractSecurityWebSocketMessageBrokerConfigurer
takes care of defining the SecurityContextChannelInterceptor
bean (https://github.com/spring-projects/spring-security/blob/4.2.10.RELEASE/config/src/main/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurer.java#L170-L173) - so you should be good to go.
okay well now that i wrote that, i re-read that you are explicitly asking for grails-2.5.x which means spring-security-core-3.2.x.
spring-security-messaging was introduced with spring-security-4.0 :(
what you could try is take a look at the SecurityContextChannelInterceptor
and create/register sth. like that yourself...
Is it sufficient to intercept the call once the Principal is established, then call springSecurityService.reauthenticate passing in principal.name? That approach works, but I'm not sure if that opens up any security holes.
no that would not be a good idea as SpringSecurityService#reauthenticate
does not clear the security context. that happens for normal web/http requests during processing of the spring security filter chain but as incoming websocket messages are no http requests, you will leak authentications - ☠️ .
ref. https://github.com/grails-plugins/grails-spring-security-core/blob/2.x/src/java/grails/plugin/springsecurity/SpringSecurityUtils.java#L585-L602
what you could do is wrap your code in SpringSecurityUtils.doWithAuth(String username, Closure closure)
as that ensure clearing/resetting the SecurityContextHolder
.
ref. https://github.com/grails-plugins/grails-spring-security-core/blob/2.x/src/java/grails/plugin/springsecurity/SpringSecurityUtils.java#L647-L662
that would look sth. like this:
class FooController {
SpringSecurityService springSecurityService
@MessageMapping("/hello")
protected String hello(String world, Principal principal) {
SpringSecurityUtils.doWithAuth(principal.name) {
println springSecurityService.currentUser
}
return "hello, ${world}!"
}
}
I only have a few websocket methods to implement for this application anyway, so that solution keeps things nice and concise without messing with the guts of spring-security. Nice work @zyro23 - thank you very much!
@zyro23, thank you again. The intent of gaining access to the User
is to prevent unauthorized subscriptions to specific destinations (as show below in listen
) so later I could do something like simpMessagingTemplate.convertAndSend("/topic/listen/${id}", message)
and it would only go to pre-authorized subscribers.
Questions:
listen()
preventing the subscription, or is there a better way?class ConversationController {
SpringSecurityService springSecurityService
ConversationService conversationService
SimpMessagingTemplate simpMessagingTemplate
@SubscribeMapping("/listen/{id}")
protected String listen(@DestinationVariable String id, Principal principal) {
SpringSecurityUtils.doWithAuth(principal.name) {
def user = springSecurityService.currentUser
if(conversationService.hasReadAccess(id, user)) {
return "welcome to conversation ${id}, ${user.username}"
}
else {
// todo: how to reject subscription?
}
}
}
@MessageMapping("/send/{id}")
protected String send(@DestinationVariable String id, String message, Principal principal) {
SpringSecurityUtils.doWithAuth(principal.name) {
def user = springSecurityService.currentUser
if(conversationService.hasWriteAccess(id, user)) {
// todo: send to conversation specific subscribers
// this destination doesn't work
simpMessagingTemplate.convertAndSend("/app/listen/${id}", message)
}
else {
// reject message
}
}
}
}
unfortunately no, that will not prevent/reject subscriptions. those have already happened then.
what you want is a channel interceptor (which is exactly what AbstractSecurityWebSocketMessageBrokerConfigurer#configureInbound
is good for (with spring-security-messaging
)..
check the code sample outlined here for kind of a hand-crafted "poor-man's impl.":
https://jira.spring.io/browse/SEC-2546?redirect=false
and a bit of context:
https://github.com/rwinch/spring-websocket-portfolio/commit/5cb4992766c4eceef3c9cc05014a9391dd4a9399#commitcomment-6819849
https://jira.spring.io/browse/SEC-2671?redirect=false
anyway, i would strongly suggest to think about upgrading to grails-3..
update: links fixed (redirect=false)..
@zyro23, what would your recommended approach be to get spring security working with plugin version 1.3.1 and grails 2.5.6? I realize I can use Principal as a way to identify the current user, but I have services that I call that reference springSecurityUser.currentUser.
Thank you for writing and supporting this plugin! Mat