Skip to content

Instantly share code, notes, and snippets.

@xmlking
Last active November 15, 2019 13:59
Show Gist options
  • Save xmlking/5f75cf24c71a75182a02 to your computer and use it in GitHub Desktop.
Save xmlking/5f75cf24c71a75182a02 to your computer and use it in GitHub Desktop.
How to secure WebSocket connections (WebSocket over STOMP) ? Grails sample app : https://github.com/xmlking/grails-batch-rest
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
'/': ['permitAll'],
'/index': ['permitAll'],
'/index.gsp': ['permitAll'],
'/stomp/**': ['ROLE_USER'], //to secure WS handshake
'/api/**': ['ROLE_USER'],
'/assets/**': ['permitAll'],
'/**/js/**': ['permitAll'],
'/**/css/**': ['permitAll'],
'/**/images/**': ['permitAll'],
'/**/favicon.ico': ['permitAll'],
'/j_spring_security_switch_user': ['ROLE_SWITCH_USER'],
'/j_spring_security_exit_user': ['permitAll'],
'/dbdoc/**': ['ROLE_IT_ADMIN'],
'/dbconsole/**': ['ROLE_IT_ADMIN'],
]
import grails.plugin.springsecurity.annotation.Secured
import org.springframework.messaging.handler.annotation.DestinationVariable
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.handler.annotation.SendTo
import org.springframework.messaging.simp.annotation.SendToUser
import org.springframework.messaging.simp.annotation.SubscribeMapping
import org.sumo.apiapp.stomp.StompExceptionHandler
import java.security.Principal
@Secured('admin')
@MessageMapping("/chat")
class MessagingController implements StompExceptionHandler {
def messagingService
def index() {
//log.error "this is error"
//throw new IllegalStateException("test IllegalStateException")
render "index page"
}
// broadcast pattern. Join/Leave Announcements, send messages
@MessageMapping("/join")
@SendTo("/topic/chat/announcements.join")
protected String join(String message, Principal principal) {
log.debug "Join Message: ${message} from ${principal.name}"
messagingService.addUser(principal.name)
Date now = Calendar.getInstance().getTime()
return "${principal.name} joined the chatroom at ${now}. Message: ${message}!"
}
@MessageMapping("/leave")
@SendTo("/topic/chat/announcements.left")
protected String leave(String message, Principal principal) {
log.debug "Goodbye Message: ${message} from ${principal.name}"
messagingService.removeUser(principal.name)
Date now = Calendar.getInstance().getTime()
return "${principal.name} left the chatroom at ${now}. Message: ${message}!"
}
@MessageMapping("/messages") //without @SendTo or @SendToUser, Default response address: /topic/chat/messages
protected String sendToAll(String message, Principal principal) {
log.debug "message: [${message}] from [${principal.name}]"
return "[${principal.name}]: ${message}"
}
// reply only to sender pattern.
@MessageMapping("/self")
@SendToUser //SendToUser's Default response address: /user/queue/chat/self
protected String sendToUser(String message, Principal principal) {
log.debug "Hello [${principal.name}] you send: ${message}"
return "Hello [${principal.name}] you send: ${message}"
}
/**
* request-reply pattern.
* @SubscribeMapping method is sent as a message directly back to the connected client and does not pass through the broker
*/
@SubscribeMapping("/rooms/{id}")
protected Set<String> getChatRooms(@DestinationVariable String id) {
log.debug "id: ${id}"
return messagingService.rooms
}
// request-reply pattern.
@SubscribeMapping("/users/{room}")
protected Set<String> getActiveUsers(@DestinationVariable String room) {
log.debug "serving users for room : ${room}"
return messagingService.getUsers(room)
}
}
import grails.async.Promise
import static grails.async.Promises.*
import grails.transaction.NotTransactional
import grails.transaction.Transactional
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.annotation.Scheduled
@Transactional
@EnableScheduling
class MessagingService {
def brokerMessagingTemplate
def users= ['All', 'user1', 'user2'] as Set
def rooms = ['Developers','Hackers', 'Stockers'] as Set
def symbols = ['AAPL','YHOO', 'GOOG'] as Set
@NotTransactional
//@Scheduled(fixedDelay=60000L)
@Scheduled(fixedRate=60000L)
void sendGreetingOnceMinute() {
log.debug "sending Scheduled greeting"
brokerMessagingTemplate.convertAndSend "/topic/self", "hello from service!"
}
@NotTransactional
Promise stock(String ticker = 'GOOG') {
task {
def url = new URL("http://download.finance.yahoo.com/d/quotes.csv?s=${ticker}&f=nsl1op&e=.csv")
Double price = url.text.split(',')[-1] as Double
return [ticker: ticker, price: price]
}
}
//Sending updates to clients periodically
@Scheduled(cron="*/60 * * * * ?")
@NotTransactional
public void sendQuotes() {
symbols.collect this.&stock each {
log.debug it.class
it.then {
log.debug it
brokerMessagingTemplate.convertAndSend "/topic/price.stock." + it.ticker, it.price
}
}
}
public Set<String> getRooms() {
rooms
}
@NotTransactional
public void addUser(String user) {
users.add(user)
}
@NotTransactional
public void removeUser(String user) {
users.remove(user)
}
public Set<String> getUsers(String room) {
log.debug "serving users for room: ${room}"
users
}
}
import org.springframework.messaging.MessagingException
import org.springframework.messaging.handler.annotation.MessageExceptionHandler
import org.springframework.messaging.simp.annotation.SendToUser
trait StompExceptionHandler {
// failure handling pattern.
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleMessagingException(MessagingException exception) {
log.error('handleMessagingException: ', exception)
return exception.failedMessage;
}
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleIllegalStateException(IllegalStateException exception) {
log.error('handleIllegalStateException: ', exception)
return exception.getMessage();
}
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(Throwable exception) {
log.error('handleException: ', exception)
return exception.getMessage();
}
}
import grails.plugin.springsecurity.annotation.Secured
import org.codehaus.jackson.annotate.JsonProperty
import org.springframework.messaging.handler.annotation.DestinationVariable
import org.springframework.messaging.handler.annotation.MessageMapping
import org.springframework.messaging.simp.SimpMessageHeaderAccessor
import org.springframework.messaging.simp.annotation.SendToUser
import org.sumo.apiapp.stomp.StompExceptionHandler
import java.security.Principal
@Secured('permitAll')
@MessageMapping("/terminal")
class TerminalController implements StompExceptionHandler {
def index() {
render "index page"
}
@MessageMapping("/input/{containerId}")
@SendToUser
protected String execute(@DestinationVariable String containerId, String input,
Principal principal, SimpMessageHeaderAccessor headerAccessor) {
def session = headerAccessor.sessionAttributes
if (!session.command) {
session.command = new StringBuilder()
}
session.command << input
if (input == "\r") {
if (principal.name != 'businessadmin') return "\r\nYou don't have access to terminal...\r\n"
def result = new StringBuilder() << "\r\n"
try {
session.command.toString().execute().text.eachLine { line ->
result << line << "\r\n"
}
} catch (Exception e) {
result << e.message + "\r\n"
} finally {
session.command.setLength(0)
}
return result
}
return input
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment