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);

Tuesday, July 26, 2016

Urlmappings break weirdly with Grails

Ever started your grails application up and recieved a vomit of stacktraces complaining about being unable to set the URLMappings Bean?

example:

Error creating bean with name 'grailsUrlMappingsHolder'

This is generally the result of the url mappings failing to "compile". Check that all your paths start with a "/" slash and any closures are correctly closed.

Wednesday, June 8, 2016

Intellij and Grails 3 Command Failure

File this one under "doh, thats obvious" but I completely spaced out as to why my Intellij commands to generate new grails resources - like creating a new taglib
 

Would fail with an error like:

"C:\Program Files\Java\jdk1.8.0_45\bin\java" -XX:+TieredCompilation [...] \lib\grails-rt.jar" org.grails.cli.GrailsCli create-taglib <className> Error |
Command [create-taglib] error: Profile [org.grails.profiles:base:3.1.6] declares and invalid dependency on parent profile [org.grails.profiles:base:3.1.6] (Use --stacktrace to see the full trace)

The solution is to ensure the intellij SDK settings are set to the same level of grails SDK specific within your gradle file. I had upgraded to grails 3.1.6 within my build, but IntelliJ uses its own settings to define which grails binary to use for generating classes.




(Notice how Intellij still thinks my project is using 3.1.8 although I had downgraded to 3.1.6 - another curious feature that may crop up later)

Friday, April 15, 2016

Switching Environments in Grails 3 BootStrap

Over the iterations of grails, the best method to do this has changed. But if you want to execute grails code only within a specific environment in grails 3, you can use the static methods on the grails.util.Environment class.

Eg:

switch (Environment.getCurrent()) {

    case Environment.DEVELOPMENT:
        20.times {
            //do some thing
        }
        break;
    case Environment.PRODUCTION:
        2.times {
            //do some other thing
        }
        break;
}

Wednesday, March 16, 2016

Gmail Oauth2 with Spring and JavaMail with Grails

Setting up Gmail Email Sending with Grails (2+, 3+) Service

Most Web applications will send some sort of email to users. Lost passwords, invitations to join. Its a good way to reach professional users. Not so great for young people (they never check them) but in order to send emails, you have to get the connection right. I didn';t build a particular groovy way, I used java code within a grails service to make a simple method available to controllers and other serivces:

interface EmailService {
    /**     
    * Send a set of emails and collect the results     
    * @param addresses list of addresses to send to
    * @param subject subject of email    
    * @param text  body of email   
    * @return a list of message IDs returned from the sending service    
    */    
    def send(List<String> addresses, String subject, String text)

    /**     
    * Setup any needed state from config files etc.     
    */    
    void initialize()
}

Most of the default grails java mail setup questions (I'm using spring mail with grails because its mostly already on the classpath) you will find involve turning off enhanced seucirty in your gmail account in order to get around the error message you will recieve on startup:

Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 534-5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbvO
534-5.7.14 SC261Te39VZ5jtNBz2mvwNtIGtZLxYulCRb8D2u6rGTAg69U2-tQsPDzI1YPgWUbVo1ZQm
534-5.7.14 MlSxYEJHBzyTk-tQKy-6GN5HACShag4XcqNYlxbyWHYvMyMICSTPuwRFzM_Rn2kUOKLcoY
534-5.7.14 hcEmEC6i4DrvVh4h8KTTdK1VxgyDwD6QzfDxWgUa0vM7ZcLRfIURv1CThW4B0G5XvVgG3a
534-5.7.14 t-wJ_9P1uU8YoJK2c-QbrhZe2H9qo> Please log in via your web browser and
534-5.7.14 then try again.
534-5.7.14  Learn more at
534 5.7.14  https://support.google.com/mail/answer/78754 h24sm11972231ioi.17 - gsmtp

Someone just tried to sign in to your Google Account someone@gmailaddress.com from an app that doesn't meet modern security standards." - Oh great, here goes my day

This cryptic error results from using a setup like (in resources.groovy):

emailService(GmailEmailService) {
                template=ref("templateMessage")
                mailSender=ref("mailSender")
            }

templateMessage(SimpleMailMessage) {
                from="email@sendergmail.com"
            }

mailSender(JavaMailSenderImpl) {
                host="smtp.gmail.com"
                port=587
                protocol="smtp"
                username="email@sendergmail.com"
                password="yourpassword"
                javaMailProperties = [
                        "mail.transport.protocol" : "smtp",
                        "mail.smtp.auth" : true,
                        "mail.smtp.starttls.enable" : "true",
                        "mail.smtp.quitwait" : true,
                        "mail.debug" : true
                ]
            }

But what if you wanted to use enhanced security? Well available in the more recent versions of JavaMail is support of Oauth2.

To begin with though, you are going to need to follow the google guidelines here to recieve api credentials for your webserver. You should end up with a set of JSON credentials for your service account. :

{
  "type": "service_account",
  "project_id": "someid",
  "private_key_id": "SOMEID",
  "private_key": "YOURKEY",
  "client_email": "someemail",
  "client_id": "someid",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "someurl"
}

There is a branch here. You can choose to give the service account Delegate domain-wide authority (which I have done) or leave it at user-interactive mode. Since I dont want to have to ask my own company user account for permissions (which makes sense for a back-end webservice making sending email from only one user), I chose  to upgrade the account to domain-wide, which can be done in the console.

Adding Domain Wide Delegation means no prompts, but using an additional secret file

We will need to use these credentials to create oauth tokens that can be sent with each senmail request. To create these tokens (oauthtoken) you can use the Google Java API. (at the time of writing, the latest central maven available was: 'com.google.api-client:google-api-client:1.21.0') . Google has a guide available - but essentially you will be using the builder to setup a trusted account.

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream(authFile)).createScoped(Collections.singleton("https://www.googleapis.com/auth/gmail.send")) //for SMTP access only

Then email setup can be pretty easy. A lot of the boiler plate of creating a javax.mail.transport can be made easier by using some Java Code provided by Google itself.

If like us, you were using google apps accounts - there is additional information required. As you may get responses like:

DEBUG SMTP: SASL: no response
DEBUG SMTP: SASL authentication failed

Things to check:

* You have allowed the client id access to the same scope you are requesting (Google Scopes listing)
* You are creating your Google credential and access token with a user to impersonate:

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream(authFile))                .createScoped(Collections.singleton("https://www.googleapis.com/auth/gmail.send"))
credential.serviceAccountUser = "someuser@yourdomain.com" //important

Final Thoughts

After going through all of this trouble, I ended up switching to using the google api client and the gmail api client for java, while still using JavaMail for convience (google has a great guide here)

By the way, I reccomend using scheduling with retries in order to make sure your customers recieve their emails. It can be really annoying when an app fails to send that crucial forgot password email - they will often leave and not come back.

Turns out doing it the right way is a bit more involved - but worth it. One day your email's wont stop randomly working when google finally axes support for the basic authentication.

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




FlowJs Upload Reciever for Grails

I chose flow.js (https://github.com/flowjs/flow.js) for my client side file uploading due to its fault tollerance. I use using the ng-flow (https://github.com/flowjs/ng-flow/) angular library that builds an excellent set of directives around the framework. The one downside is the lack of documentation on what it takes to build a server side compliment to this. The particular beckend I am using is Grails3 based (although this would work for earlier versions of grails with little modification).

I built a service that can be plugged into multiple controllers to achieve the effect. While I wont publish the whole service, here is a skelleton that should be enough to get you started.