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.
Showing posts with label angular. Show all posts
Showing posts with label angular. Show all posts
Tuesday, February 14, 2017
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);
Wednesday, February 17, 2016
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.
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.
Subscribe to:
Posts (Atom)