Dropwizard metrics in Graphite - jersey

we have multiple API endpoints created with Jersey with a web.xml setup rather than the resource config setup.
We want to capture and display metrics for all requests for each endpoint including all of the different response codes.
So far I have create a class that extends the InstrumentedFilterContextListener and has the Graphite reporter in it.
In the web.xml I have added the following blocks to get the reporting working:
<listener>
<listener-class>metrics.MyInstrumentedFilterContextListener</listener-class>
</listener>
<filter>
<filter-name>testFilter</filter-name>
<filter-class>com.codahale.metrics.servlet.InstrumentedFilter</filter-class>
<init-param>
<param-name>name-prefix</param-name>
<param-value>testEndpoint</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>testFilter</filter-name>
<url-pattern>/api/testEP1/*</url-pattern>
</filter-mapping>
enter code here
<filter>
<filter-name>testFilter2</filter-name>
<filter-class>com.codahale.metrics.servlet.InstrumentedFilter</filter-class>
<init-param>
<param-name>name-prefix</param-name>
<param-value>testEndpoint2</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>testFilter2</filter-name>
<url-pattern>/api/testEP2/*</url-pattern>
</filter-mapping>
So with the above config and the class below I am getting some information in the Graphite Dashboard:
public class MyInstrumentedFilterContextListener extends InstrumentedFilterContextListener {
public static MetricRegistry METRIC_REGISTRY = new MetricRegistry();
static {
final Graphite graphite = new Graphite(new InetSocketAddress("localhost", 8080));
final GraphiteReporter reporter = GraphiteReporter.forRegistry(METRIC_REGISTRY)
.prefixedWith("API.Metrics")
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build(graphite);
reporter.start(10, TimeUnit.SECONDS);
}
#Override
protected MetricRegistry getMetricRegistry() {
return METRIC_REGISTRY;
}
}
My issue at the minute is that from what I can tell it is mostly machine related metrics, cpu, memory etc., that are being captured and not things like the number of ok, error or not found requests for the endpoints I have mapped.
I have tested that the requests are being captured by my listener class by adding a ConsoleReporter too so that I could see all of the counts for the different requests increasing.
So my question, finally, is how do I see all of the same Meter and Timer data in Graphite? Are there some extra settings I have to set or something to get this data or is the data just called something completely different in Graphite?

The issue was that the socket specified in this line was incorrect but the only way to find the socket was to use netstat and it ended up being 22033 in my case:
final Graphite graphite = new Graphite(new InetSocketAddress("localhost", 8080));
This might have been specified in some file within graphite but i did not find it mentioned in any documentation.

Related

using shiro with stormpath for jax-rs rbac

I'm attempting to adapt this excellent stormpath post by Brian Demers - https://stormpath.com/blog/protecting-jax-rs-resources-rbac-apache-shiro - to my own purposes and so far it works pretty well - except that now I want to add stormpath for user/role management rather then having the users in a shiro-ini file.
I'm using Apache Shiro shiro-jaxrs 1.4.0-RC to secure a REST endpoint using jax-rs. It works fine. I'm able to selectively secure the endpoints using a #RequiresPermissions tag like so:
#Path("/scan")
#Produces("application/json")
public class ScanService {
final static Logger logger = Logger.getLogger(ScanService.class);
#GET
#Path("/gettest")
#RequiresPermissions("troopers:read")
public List<Barcode> gettest() throws Exception {
ArrayList<Barcode> listofstrings = new ArrayList<Barcode>();
Barcode b = new Barcode();
b.setBarcode("this is a big barcode");
listofstrings.add(b );
return listofstrings;
}
#GET
#Produces( MediaType.APPLICATION_JSON )
#Path("/gettest2")
public List<Barcode> gettest2() throws Exception {
ArrayList<Barcode> listofstrings = new ArrayList<Barcode>();
Barcode b = new Barcode();
b.setBarcode("this is a BIGGER barcode");
listofstrings.add(b );
return listofstrings;
}
I also have an application class to add my resource and the ShiroFeature class like so:
package ca.odell.erbscan;
import ca.odell.erbscan.ws.ScanService;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.web.jaxrs.ShiroFeature;
import com.stormpath.shiro.jaxrs.StormpathShiroFeature;
#ApplicationPath("/")
public class ERBApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<Class<?>>();
// register Shiro
classes.add( ShiroFeature.class);
// register resources
classes.add(ScanService.class);
return classes;
}
}
and my web.xml to init my Application class like so:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>ERBSCAN</display-name>
<servlet>
<servlet-name>ERBRest</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>ca.odell.erbscan</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>ca.odell.erbscan.ERBApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ERBRest</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
</web-app>
and finally my shiro.ini
[main]
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.sessionIdCookieEnabled = false
securityManager.sessionManager.sessionIdUrlRewritingEnabled = false
[urls]
/** = noSessionCreation, authcBasic[permissive]
[users]
# format: username = password, role1, role2, ..., roleN
root = secret,admin
emperor = secret,admin
officer = secret,officer
guest = secret
[roles]
admin = *
officer = troopers:create, troopers:read, troopers:update
What I want to do next is add Stormpath for RBAC rather then having users and roles in a file. My feeling is there's a simple way to do this and that I'm overthinking it.
I thought it would be a fairly straightforward manner of adding in my shiro.ini:
stormpathClient = com.stormpath.shiro.client.ClientFactory
stormpathClient.cacheManager = $cacheManager
stormpath.application.href=http://....
But I was wrong. Could someone point me in the right direction?
thanks for reading that post!
A couple things I want to point out:
Use this feature com.stormpath.shiro.jaxrs.StormpathShiroFeature
instead of ShiroFeature
Your shiro.ini could look something like:
[main]
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.sessionIdCookieEnabled = false
securityManager.sessionManager.sessionIdUrlRewritingEnabled = false
[urls]
/** = noSessionCreation, authcBasic[permissive]
[stormpath]
stormpath.application.href=http://....
Permissions can be stored as user or role Custom Data, you can update the Custom Data in the Stormpath admin console:
{
… your other custom data fields …,
"apacheShiroPermissions": [
"troopers:create",
"troopers:read",
"troopers:update"
]
}
This blog post covers the custom data bit, it is a little older, but still relevant. I'll be updating the doc on this in the near future, so feedback welcome.
If this doesn't help you can also ping support, and we will get you going!
I'm going to answer my own question here. I don't think this is the best solution, but it something I managed to get to work.
I followed this web app tutorial off of the shiro site.
https://shiro.apache.org/webapp-tutorial.html
I checked out step6 of the project and copied the [main] section of the shiro.ini as follows: Note I added the
https://api.stormpath.com/v1/applications/$STORMPATH_APPLICATION_ID
at the bottom the [main] section.
cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $cacheManager
stormpathClient = com.stormpath.shiro.client.ClientFactory
stormpathClient.cacheManager = $cacheManager
# we can disable session tracking completely, and have Stormpath manage it for us.
sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager
securityManager.sessionManager = $sessionManager
securityManager.sessionManager.sessionIdCookieEnabled = false
securityManager.sessionManager.sessionIdUrlRewritingEnabled = false
stormpathRealm = com.stormpath.shiro.realm.ApplicationRealm
stormpathRealm.client = $stormpathClient
stormpathRealm.groupRoleResolver.modeNames = name
securityManager.realm = $stormpathRealm
stormpathRealm.applicationRestUrl = https://api.stormpath.com/v1/applications/$STORMPATH_APPLICATION_ID
I then completely removed the [users] section of the shiro.ini. Since it's now wired up to Stormpath, I need to add users and groups there. My ScanService ( as above ) has a method called gettest decorated thusly:
#GET
#Path("/gettest")
#RequiresPermissions("trooper:read")
public List<Barcode> gettest() throws Exception {
.
.
.
so I need to added an account, a group and permissions in stormpath to match the permissions on the above resource. In order to do this, I need to add an account in Stormpath ( I already have the Application setup ) under my existing test application. I also added a group called officer1. The under this group I added Custom Data an array called apacheShiroPermissions - I added a string key/value pair 'trooper:read' to the apacheShiroPermissions - the JSON is below
{
"apacheShiroPermissions": [
"trooper:read"
]
}
Then I simply made sure my account - in this case jlpicard was part of the officer1 group.
Testing with curl
curl --user jlpicard:Changeme1 http://localhost:8080/JPA1_Web_exploded/rest/scan/gettest
Confirms jlpicard has access on the permission level. Adding and removing the strings entry's to the apacheShiroPermission array i.e. allows that fine grained access.
Also removing jlpicard from the officer1 or adding another account to it works as expected.
There is undoubtedly a better way to do this but this has what was worked for me so far.

Jetty websocket class loading issue

I have implemented a basic websocket server in Jetty(Standalone mode).
MyWebSocketServlet.java
public class MyWebSocketServlet extends WebSocketServlet {
#Override
public void configure(WebSocketServletFactory webSocketServletFactory){
webSocketServletFactory.getPolicy().setIdleTimeout(1000 * 10 * 60);
webSocketServletFactory.setCreator(new MyWebSocketFactory());
}
}
MyWebSocketFactory.java
public class MyWebSocketFactory implements WebSocketCreator {
public Object createWebSocket(
ServletUpgradeRequest servletUpgradeRequest
, ServletUpgradeResponse servletUpgradeResponse) {
return new MyWebSocketListener();
}
}
MyWebSocketListener.java
public class MyWebSocketListener implements WebSocketListener {
private Session sessionInstance;
public void onWebSocketBinary(byte[] bytes, int i, int i1) {
ByteBuffer data = ByteBuffer.wrap(bytes, i, i1);
try {
sessionInstance.getRemote().sendBytes(data);
} catch (IOException e) {
e.printStackTrace();
}
}
public void onWebSocketClose(int i, String s) {
}
public void onWebSocketConnect(Session session) {
sessionInstance = session;
}
public void onWebSocketError(Throwable throwable) {
throwable.printStackTrace(System.err);
}
public void onWebSocketText(String s) {
try {
sessionInstance.getRemote().sendString(s);
} catch (IOException e) {
e.printStackTrace();
}
}
}
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false"
version="3.1">
<servlet>
<servlet-name>WsEcho</servlet-name>
<servlet-class>org.test.sanket.MyWebSocketServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WsEcho</servlet-name>
<url-pattern>/echo/*</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>HttpEcho</servlet-name>
<servlet-class>org.test.sanket.MyHttpServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HttpEcho</servlet-name>
<url-pattern>/httpecho/*</url-pattern>
</servlet-mapping>
</web-app>
Instead of using a Standalone Jetty if I use embedded jetty and programatically configure the server and add the Servlets then this sample runs fine.
But if I am packaging the same as a war, and then deploying the same in a standalone jetty instance I am having the following observation:
I am able to hit the HttpServlet , i.e. MyHttpServlet and receive a response
But when I try to hit the websocket servlet, i.e. MyWebSocketServlet, I am seeing the following error:
exception
java.lang.ClassCastException: org.eclipse.jetty.server.HttpConnection cannot be cast to org.eclipse.jetty.server.HttpConnection
at org.eclipse.jetty.websocket.server.WebSocketServerFactory.acceptWebSocket(WebSocketServerFactory.java:175)
at org.eclipse.jetty.websocket.server.WebSocketServerFactory.acceptWebSocket(WebSocketServerFactory.java:148)
at org.eclipse.jetty.websocket.servlet.WebSocketServlet.service(WebSocketServlet.java:151)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:566)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1111)
I did come across the following link:
Jetty - stand alone WebSocket server
From the above link it seems to be a class loading issue, because jetty websocket package is treated as system class package and shouldn't be loaded by the WebApp if already loaded by the system.
So as referenced in the above link, I looked into the details suggested at:
http://www.eclipse.org/jetty/documentation/9.2.10.v20150310/jetty-classloading.html
From this link, one of the ways to get around this issue is to call the org.eclipse.jetty.webapp.WebAppContext.setSystemClasses(String Array) or org.eclipse.jetty.webapp.WebAppContext.addSystemClass(String) to allow fine control over which classes are considered System classes.
So for being able to do that, I should be able to get an Instance of WebAppContext, when Jetty is initializing and add the WebSocket classes as system classes.
I tried searching for how one would be able to achieve the same but no luck so far ? Can anybody kindly point me to a reference implementation as to how this can be achieved ?
Java Version: OpenJDK 7(latest)
Jetty: 9.2.10.v20150310
Operating System: Ubuntu 14.04
Thanks in advance!
If you have followed this link to setup the Jetty Standalone Instance, then you might have run the following command:
[/opt/web/mybase]# java -jar /opt/jetty/jetty-distribution-9.2.10.v20150310/start.jar --add-to-start=deploy,http,logging
If so, then when you try to hit the websocket servlet you will see the exception that you are noticing.
All you need to do is, instead of that command, you as well need to initialize the websocket module as shown below:
[/opt/web/mybase]# java -jar /opt/jetty/jetty-distribution-9.2.10.v20150310/start.jar --add-to-start=deploy,http,logging,websocket
Hope this helps!
Don't include the org.eclipse.jetty.* classes in your war's WEB-INF/lib or WEB-INF/classes directories.

Integrating spring and vaadin

Is it good to integrate Spring and vaadin? I am looking to use vaadin in view layer and spring for my services. So far I am not able to find any neat solution for integration. Is it even a good idea for Production applications like management solutions or ERP?
what could be the design of the application?
How to keep clear separation between application layers?
Issues with Vaadin integration with spring security?
How to manage scope of spring beans?
Also could anyone share the advantages and disadvantages of this integration over spring MVC.
You have a very useful add-on for Vaadin called SpringVaadinIntegration.
You can keep a clean separation very easily with Vaadin, just use Spring #Autowired and services for the data retrieval and modification.
I've used Spring security and I had no problems with Vaadin.
You can manage the scope with the #Scope annotation, with three differents values, if I remember correctly: Singleton (default), Prototype and Session.
Did you consider using Vaadin UIProvider mechanism. This way autowiring in UI is totally transparent.
You can have a look at a really simple example that uses this solution on github: spring-vaadin-example
You don't need any special vaadin addons for spring at all. Just use aspectj and #Configurable annotation along with #Autowired for every component you want to integrate with spring. Like this:
#Configurable(preConstruction = true)
public class LoginUserPasswdDialog extends LoginDialogBase {
static final Logger log = Logger.getLogger(LoginUserPasswdDialog.class);
#Autowired
private AppConfig config;
#Autowired
UserFactory userFactory;
StringBuffer name;
LoggedAction action;
protected Window parent = null;
protected Button ok;
protected Label l;
protected TextField nameText;
protected PasswordField password;
protected CheckBox saveUserPass;
protected final Window w = new Window("");
#SuppressWarnings("serial")
public void create(AbstractComponent component) throws Exception {
parent = component.getWindow();
VerticalLayout v = new VerticalLayout();
v.setSizeFull();
v.setSpacing(true);
l = new Label(
_.getString("LoginUserPasswdDialog.0"), Label.CONTENT_XHTML); //$NON-NLS-1$
l.setSizeFull();
l.addStyleName("centeredLabel");
v.addComponent(l);
HorizontalLayout h = new HorizontalLayout();
h.setMargin(true);
h.setSpacing(true);
nameText = new TextField();
nameText.setWidth("100%");
v.addComponent(nameText);
nameText.focus();
password = new PasswordField();
password.setWidth("100%");
v.addComponent(password);
saveUserPass = new CheckBox(_.getString("LoginUserPasswdDialog.1")); //$NON-NLS-1$
v.addComponent(saveUserPass);
v.setComponentAlignment(saveUserPass, Alignment.MIDDLE_RIGHT);
ok = new Button(_.getString("LoginUserPasswdDialog.2")); //$NON-NLS-1$
ok.setWidth("100px");
ok.setClickShortcut(KeyCode.ENTER);
h.addComponent(ok);
h.setComponentAlignment(ok, Alignment.MIDDLE_CENTER);
v.addComponent(h);
v.setComponentAlignment(h, Alignment.MIDDLE_CENTER);
Cookie nameCookie = CookieUtils.getCookie("username");
Cookie passCookie = CookieUtils.getCookie("password");
if (nameCookie != null && passCookie != null) {
nameText.setValue(nameCookie.getValue());
password.setValue(passCookie.getValue());
saveUserPass.setValue(true);
}
w.setWidth("400px");
w.setCaption(config.getTitle() + _.getString("LoginUserPasswdDialog.4"));
w.setResizable(false);
w.setClosable(false);
w.setModal(true);
w.center();
ok.addListener(new ClickListener() {
public void buttonClick(ClickEvent event) {
String name = (String) nameText.getValue();
String pass = (String) password.getValue();
User u = userFactory.getUser(name, pass);
if (u != null) {
if ((Boolean) saveUserPass.getValue()) {
CookieUtils.makeCookie("username", name);
CookieUtils.makeCookie("password", pass);
} else {
CookieUtils.deleteCookie("username");
CookieUtils.deleteCookie("password");
}
userFactory.updateUser(u);
action.loggedIn(u);
parent.removeWindow(w);
return;
} else {
password.setValue("");
WaresystemsUI.handle
.get()
.getMainWindow()
.showNotification(
"",
_.getString("LoginUserPasswdDialog.3"), Notification.TYPE_ERROR_MESSAGE); //$NON-NLS-1$
return;
}
}
});
w.addComponent(v);
parent.addWindow(w);
}
#Override
public void setAction(LoggedAction loggedAction) {
this.action = loggedAction;
}
}
Of course you need add support to the web.xml:
<!-- SPRING -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:META-INF/spring/application-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
<init-param>
<param-name>threadContextInheritable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Take a look also on Spring UI scope add-on http://vaadin.com/addon/spring-ui-scope
The add-on defines custom Spring scope: UI-scope, which pass well with Vaadin application.
There is also sample application using the scope.

Jetty 9 WebSocket Server Max Message Size on Session

I ran into this issue and had some difficulty finding answers for this anywhere so I thought I would enter it here for future programmers.
In Jetty 9, if you try to set the maximum message size on a session object to handle large data packets, it will not work. You will still get disconnected if your client tries to send large data. I'm talking about setMaximimumMessageSize on this object: http://download.eclipse.org/jetty/stable-9/apidocs/org/eclipse/jetty/websocket/api/Session.html
Instead, what you have to do is set the max message size on the policy object acquired from the WebSocketServletFactory.
public final class MyWebSocketServlet extends WebSocketServlet
{
private static final long MAX_MESSAGE_SIZE = 1000000;
#Override
public void configure(WebSocketServletFactory factory)
{
factory.getPolicy().setMaxMessageSize(MAX_MESSAGE_SIZE);
factory.setCreator(new MyWebSocketCreator());
}
}
This will work as intended and your server will be able to handle large messages up to the maximum size you set.
The way you are setting the maximum message, in the WebSocketServlet is correct.
The Session.setMaximumMessageSize(long) as you pointed out in the javadoc is an unfortunately leaking of an early draft of JSR-356 (javax.websocket API) effort.
That method on the Jetty side API should not be there, and has been removed in Jetty 9.1
Bug has been filed: https://bugs.eclipse.org/bugs/show_bug.cgi?id=412439
Note: Jetty 9.1 will have the JSR-356 (javax.websocket API) support in it. Where the javax.websocket.Session has 2 methods of similar behavior.
javax.websocket.Session.setMaxBinaryMessageBufferSize(int)
javax.websocket.Session.setMaxTextMessageBufferSize(int)
I had this problem when sending files (binary data) with more than 64KB. I was using the javax.websocket-example from the Embedded Jetty WebSocket Examples.
Finally the only thing I need to do was to setMaxBinaryMessageBufferSize in the Session argument from the #OnOpen annotated method.
#ClientEndpoint
#ServerEndpoint(value = "/ws")
public class EventSocket {
#OnOpen
public void onWebSocketConnect(Session sess) {
sess.setMaxBinaryMessageBufferSize(1 * 1024 * 1024); // 1MB
}
#OnMessage
public void processUpload(byte[] b, boolean last, Session session) {
...
}
}
If anybody wants configurable alternative, setting servlet parameter maxTextMessageSize in web.xml also works -
<servlet>
<servlet-name>MyWebSocketServlet</servlet-name>
<servlet-class>test.MyWebSocketServlet</servlet-class>
<init-param>
<param-name>maxTextMessageSize</param-name>
<param-value>1048576</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyWebSocketServlet</servlet-name>
<url-pattern>/MyWebSocket/*</url-pattern>
</servlet-mapping>

Spring #Secured method is not working when called through a filter that uses a Flying Saucer (Itext) ReplacedElementFactory implementation

When I call a Spring #Secured method that is found on a #Service class, through a normal #Controller class, the authentication is working correctly.
When I call the same method through an IText PDF filter, using a org.xhtmlrenderer.extend.ReplacedElementFactory implementation, I get the following stack trace:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.credentialsNotFound(AbstractSecurityInterceptor.java:325)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:196)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
The security is obviously working because users without the required roles receive an Access Denied Exception, while other users with the correct roles have no issues at all.
Here is a snippet from my web.xml:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
<filter-name>pdfFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>pdfFilter</filter-name>
<url-pattern>/reports/pdf/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Here is a snippet of the ReplacedElementFactory implementation:
#Inject private ImageService imageService;
#Override
public ReplacedElement createReplacedElement(LayoutContext ctx, BlockBox box, UserAgentCallback uac, int width, int height) {
Element el = box.getElement();
if (el == null) {
return null;
}
String nodeName = el.getNodeName();
if (nodeName.equalsIgnoreCase("img")) {
String srcAttr = el.getAttribute("src");
FSImage fsImage;
try {
fsImage = getImage(srcAttr, uac);
}catch(BadElementException ex) {
fsImage = null;
}catch(IOException ex) {
fsImage = null;
}catch(NullPointerException ex) {
fsImage = null;
}
if (fsImage != null) {
if (width != -1 || height != -1) {
fsImage.scale(width, height);
}
return new ITextImageElement(fsImage);
}
}
return null;
}
private FSImage getImage(String src, UserAgentCallback uac) throws IOException, BadElementException, NullPointerException {
FSImage fsImage;
String[] split = src.split("/");
if (src.contains("image/person/")) {
Long id = Long.valueOf( split[split.length - 1] );
Image img = imageService.getPersonImageByImageId(id);
fsImage = new ITextFSImage(com.lowagie.text.Image.getInstance(img.getImage()));
return fsImage;
}
Here is my ImageService class method:
#Secured({"ROLE_MY_ROLE_READ"})
public Image getPersonImageByImageId(Long imageId) {
return imageDao.findOne(imageId);
}
The failure happens on the call to the image service method, assumably because it is Secured and the ReplacedElementFactory implementation does not have access to the security context, but how do I authenticate?
I am new to posting, so please let me know if there is anything else required.
Not sure how you have configured your security to kick in. but if you have used for that also then check the ordering of the filter. The security filter should be before your pdffilter.

Resources