Ever needed to deliver images securely? I had a usecase for delivering map tiles only if the user was authenticated - but I was using stateless authentication with Java Web Tokens (JWT). In order to use web tokens I need to place an Authentication header on each web request - easy enough. But that isnt possible to do when using a html img tag - it only allows for a url souce.
So you have two options - extend your JWT token reading service to allow it to read tokens from the url params object too (ie <img src='someurl?token=foo'> ) or instead of using the standard src attribute - use javascript to securely download the image data into a blob, and use the html5 blob data attribute for img tags.
This angular directive will do such a thing. I based my solution on this source code- except noting adding that I use an angular service "Authentication" that has a promise that will resolve only if there is valid non-expired authentication (and will refresh the token if expired).
So I only needed to modify the directive's core method to add my extra waitForAuthenticated() promise.
attrs.$observe('httpSrc', function (url) {
revokeObjectURL();
if (url && url.indexOf('data:') === 0) {
$scope.objectURL = url;
} else if (url) {
Authentication.waitForAuthenticated().then(function(ok){
$http.get(url, {
responseType: 'arraybuffer',
headers: {
'accept': 'image/webp,image/*,*/*;q=0.8'
}
})
.then(function (response) {
var blob = new Blob(
[response.data],
{type: response.headers('Content-Type')}
);
$scope.objectURL = URL.createObjectURL(blob);
});
});
}
});
The blue text is the core of the directive, pipeling the securely delivered image data into an HTML objectURL. You can attach headers easily using an injected interceptor to all $http requests.. This wont work on older browsers not supporting the objectURL standard.
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
}
}
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.
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.
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.
Defining a listener to updates from other requests as a service is easy.
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.
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.
You'll need to create a pretty extensive configuration bean to integrate spring websocket and spring security rest:
Your configuration bean
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.
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.
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.
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.
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:
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
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.
Subscribe to:
Posts (Atom)