Showing posts with label spring. Show all posts
Showing posts with label spring. Show all posts

Tuesday, February 14, 2017

A working combination of grails 3, geoDB & hibernate spatial.

Ever since the move to grails 3 - dealing with plugins (for me) has become a pain. Almost all the documentation out there is for the now legacy 2.X branches. Makes me consider just using basic servlets like jetty or tomcat embedded. Anyway!

Hibernate spatial is helpful ORM when working with the Java Topology Suite - and since hibernate 5 is now worked into the core project.

To setup your grails project to use hibernate spatial - and for grails to successfully select the type of the JTS Geometry derviatives when defined in a domain class - you'll need to make sure all your hibernate versions stack up right to avoid no class data found or no class def found, or java.lang.NoSuchMethodError OR cannot resolve name errrors - all symptoms of mismatch java versions and/or class/interface definitions.

in your build gradle file:
(I happen to be using the latest grails 3.2.6)

* Make sure you are using the hibernate5 grails plugin - atleast version 6.0.0
* Make sure the version of hibernate-core & hibernate-ehcache & hibernate-spatial matches - atleast version 5.1.0 - but crucially - not greater than 5.2.0 - 5.2.0+ is currently incompatible with grails.
* Make sure you are using geoDB version 0.7 (for local testing) - but not version 0.8 (incompatible from my testing)

* In your bootstrap, make sure to upgrade the h2 database to contain goeDB features:

 try {
            def sql = new Sql(dataSource)
            sql.executeUpdate('CREATE ALIAS InitGeoDB for "geodb.GeoDB.InitGeoDB"')
            sql.executeUpdate('CALL InitGeoDB()')
        } catch (java.sql.SQLException e) {
            log.debug('', e)
        }

And thats it. After changing the plugins, make sure to do a gradle clean, and you should be able to correctly map classes like:

import com.vividsolutions.jts.geom.Geometry
import com.vividsolutions.jts.geom.GeometryFactory

class Feature {

    UUID id

    double lat
    double lon

    Geometry geometry

    def beforeInsert( ) {
        if ( lat!=null && lon!=null && geometry == null) {
            geometry = (new GeometryFactory()).createPoint( new Coordinate(lon, lat ))
        }
    }

    static mapping = {
        id(generator: "uuid2", type: "uuid-binary") // H2 & Postgres
    }

}

Tuesday, December 20, 2016

Grails 3 Push Notifications: Spring Web Sockets, AngularJS, JWT.

Apparantly its not very common to want to combine these sets of technologies. But I did, and I wanted to share some of the tips I found, and hopefully combine the knowledge into one place. There are a bunch of different sources that will help you solve several problems you may encounter.

The use-case


I wanted to be able to have my API server push updates to my web-based angular app as interesting things took place. The most common use is that someone else posted new messages in a chat thread - but it can also happen when someone else updates a shared resource or other collaboration. Or perhaps even to populate new items into a news feed without needing a page refresh.

To "push" data to my angular app, I knew I was going to need sockets. Grails 3 has updated spring support that allows for the use of spring websockets - which use the STOMP protocol & SockJS. SockJS is a library that abstracts away a lot of the lower level websocket management in browsers and allows fallbacks to long polling if needed. STOMP makes it easy to create per-user topics and other utility methods.

The websocket pattern integrates really nicely with grails reactor integration. As controllers recieve updates from other clients, events can be raised to push notifications to interested listeners.


Starting Out


Understanding the basics of web sockets are outside the scope of this post - but spring has made integrating web sockets over STOMP easy in recent versions.

There are a couple good sources for starting out. There is an up-to-date grails 3 plugin that allows you to use socket topics easily from within existing controllers.

Defining a controller that takes incoming chat requests over socket is easy.
class ChatController {

    def springSecurityService    def messagingService
    def index = {}

    @ControllerMethod    
    @MessageMapping("/incchat")
    @PreAuthorize("hasRole('ROLE_USER')")
    @SendToUser("/queue/incchat")
    /**     * Take a JSON input and parse from object
     * id: threadid, text: messageText     */    
    String doChat(String jsonIn, Principal principal) {
     ...


Defining a listener to updates from other requests as a service is easy.
@Consumer
class WebChatService {

    SimpMessagingTemplate brokerMessagingTemplate
    @Selector('message.received.chat')
    void sendChatToUser(WebChatTarget target) {
        String data = [message: target.message, thread: target.messageThread] as JSON
        brokerMessagingTemplate.convertAndSendToUser(target.username, "/queue/chat", data)
    }
}

SOCKJS allows http websocket upgrades - so make sure to use a http:// base url if your sockets are using sockjs. Use ws:// otherwise. Make sure to allow origins that would allow your angular app to connect if its running on a different host.

@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
    stompEndpointRegistry.addEndpoint("/stomp")
            .setAllowedOrigins("*")
            .withSockJS() //SOCKJS requires http connection, other use ws://
            .setSessionCookieNeeded(false);
}

Then it gets tricky.


Everything works amazingly up to this point if you are using session based authentication. I'm not doing that in the Yolobe stack. Our api is secured via stateless JWT to make mobile integration easier  -you just need a couple services and an http interceptor to have angular send the correct headers every time. But what about Web sockets - can you send the right headers to have JWT work on websocket? Kind of.

Spring sockets was recently updated to allow JWT type tokens. The provided example will allow basic JWT authentication to work. But user-based topics and queues will stop functionining as the principal user is missing from the session. Youll have to inject it on every call.

A full solution


You'll need to create a pretty extensive configuration bean to integrate spring websocket and spring security rest:

Your configuration bean

webSocketConfig(YolobeWebSocketConfigurationBean) {
    jwtService = ref("jwtService")
}

Will need to parse JWT tokens on each request (inside configureClientInboundChannel )- assign the principal to the session/message, then forward the request to a new principal-aware user registry.

Configuration Bean

@Configuration
@EnableWebSocketMessageBroker
@Order(HIGHEST_PRECEDENCE)
public class YolobeWebSocketConfigurationBean extends AbstractWebSocketMessageBrokerConfigurer {

    public DefaultSimpUserRegistry _userRegistry = new DefaultSimpUserRegistry();
    public DefaultUserDestinationResolver _resolver = new DefaultUserDestinationResolver(_userRegistry)


    JwtService jwtService    
    MessageBrokerRegistry messageBrokerRegistry

    @Bean    
    @Primary    
     public SimpUserRegistry userRegistry() {
        return _userRegistry;
    }

    @Bean
    @Primary
    public UserDestinationResolver userDestinationResolver() {
        return _resolver;
    }

    def tokenPattern = ~/Bearer\s(?<token>\S+)/    
    private Principal getUser(StompHeaderAccessor accessor) {
        String header = accessor.getNativeHeader("Authorization")[0]
        def matcher = (header =~ tokenPattern)
        if (matcher.matches()) {
            try {
                JWT jwt = jwtService.parse(matcher.group('token'))
                return new SocketPrincipal(username:jwt.payload.toJSONObject()["sub"]);

            }
            catch (JOSEException formatError) {
                throw new Exception("Bad Authorization: Bearer - Token")
            }
        }
        throw new Exception("Bad Authorization: Bearer - Token")
    }


    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {


        registration.setInterceptors(new ChannelInterceptorAdapter() { //authenticate user on connection request            

                @Override
                public Message<?> preSend(Message<?> message, MessageChannel channel) {

                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);

                Principal yourAuth = getUser(accessor)

                if (accessor.messageType == SimpMessageType.CONNECT) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionConnectedEvent(this, message, yourAuth));
                } else if (accessor.messageType == SimpMessageType.SUBSCRIBE) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionSubscribeEvent(this, message, yourAuth));
                } else if (accessor.messageType == SimpMessageType.UNSUBSCRIBE) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionUnsubscribeEvent(this, message, yourAuth));
                } else if (accessor.messageType == SimpMessageType.DISCONNECT) {
                    YolobeWebSocketConfigurationBean.this._userRegistry.onApplicationEvent(new SessionDisconnectEvent(this, message, accessor.sessionId, CloseStatus.NORMAL));
                }


                accessor.setUser(yourAuth);
                accessor.setLeaveMutable(true);
                return MessageBuilder.createMessage(message.payload, accessor.messageHeaders)
            }
        });
    }

    @Override    
    public void configureMessageBroker(MessageBrokerRegistry messageBrokerRegistry) {
        messageBrokerRegistry.enableSimpleBroker("/queue", "/topic");
        messageBrokerRegistry.setUserDestinationPrefix("/user")
        messageBrokerRegistry.setApplicationDestinationPrefixes("/app");
        this.messageBrokerRegistry = messageBrokerRegistry;
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        stompEndpointRegistry.addEndpoint("/stomp")
                .setAllowedOrigins("*")
                .withSockJS() //SOCKJS requires http connection, other use ws://                .setSessionCookieNeeded(false);
    }

    @Bean
    public GrailsSimpAnnotationMethodMessageHandler grailsSimpAnnotationMethodMessageHandler(SubscribableChannel clientInboundChannel, MessageChannel clientOutboundChannel, SimpMessagingTemplate brokerMessagingTemplate) {
        GrailsSimpAnnotationMethodMessageHandler handler = new GrailsSimpAnnotationMethodMessageHandler(clientInboundChannel, clientOutboundChannel, brokerMessagingTemplate);
        handler.setDestinationPrefixes(["/app","/queue","/topic"]);
        return ((GrailsSimpAnnotationMethodMessageHandler) (handler));
    }

}

Client side

I used this fork of ng-stomp that allows multiple socket connections and disconnection notifications. here is some ugly sample code that shows what is possible. Credentials.decorate() is an angular service that holds stored credentials (persisted to localstorage) and creates a header map.

decorate :  function() {
    return { 'Authorization': this.getType() + " " + this.getToken() };
}

$stomp
    .connect("messaging", platformApiEndpoint.host+'/stomp',  Credentials.decorate() )
    .then(function (frame) {
        console.log(frame);        // notify callback function
        var showResponse = function (res) {
            console.log("RESPONSE! OMG", JSON.stringify(JSON.parse(res.body)));        
        };        
        $stomp.subscribe("messaging", '/user/queue/chat', Credentials.decorate()).then(null, null, showResponse);
        $timeout(function() { //example sending a message to topic            
                $stomp.send("messaging", '/app/incchat', {
                      id: $scope.threadId,                text: "connected VIA SOCK"            
                }, Credentials.decorate() );
        }, 1000);

Wednesday, February 17, 2016

Grails3 and Spring Security

ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[grailsDispatcherServlet] - Servlet.service() for servlet grailsDispatcherServlet threw exception
java.lang.IllegalStateException: There was a problem retrieving the current GrailsWebRequest. This usually indicates a filter ordering issue (the 'springSecurityFilterChain' filter-mapping element must be positioned after the 'grailsWebRequest' element when using @Secured annotations).

This issue was bugging me on a new grails3 and spring security web application. It turns out there are more than few active bugs on this issue. I filed my own:

https://github.com/grails-plugins/grails-spring-security-core/issues/412

After a week of noodling on the issue and trying various leads - the root cause seems to be spring security is delegating to the embedded tomcat error pages for routes only covered by static rule mappings (while your application is still using annotations). Instead, the plugin should correctly delegate to your mapped errors pages within the grails system. Included in the above post is my work around, which I will repeat here. This will cause all errors encountered within static routes to use the correct mappings.

You must setup an error controller:

class ErrorController {

    def error() {
        render view: 'error'    }

    def invalid() {
        render view: 'error'    }

    def denied() {
        render view: 'denied'    }

    def notFound() {
        render view: 'notFound'    }
}

And use this controller within your URL mappings:

"400"(controller: "error", action: "invalid")
"500"(controller: "error", action: "denied")
"403"(controller: "error", action: "denied")

"404"(controller: "error", action: "notFound")


And finally disable the spring-secuurity error page within application.groovy

grails.plugin.springsecurity.adh.errorPage = null