In my application I would like to log request and response with business logic. Application does some backend logic with SOAP communication and this is what I want to log.
Unfornatly in this same application I must serve data ( avg. is 4 request/s)
Let's say this service name is PlatformDataService.
How to turn off cxf logging for only one web service?
Enviorment:
JDK 8
cxf 2.7.14
AS : Jboss EAP 6.4
I turn on login on server by:
add logger org.apache.cxf INFO
and system property org.apache.cxf.logging.enabled=true
You can extend LoggingInInterceptor and LoggingOutInterceptor. Based on the soapAction you can ignore only logging for one service.
Extend LoggingOutInterceptor for all the outbound requests and override method handleMessage like this.
private boolean doNotLog=false;
public void handleMessage(Message message) throws Fault {
TreeMap<String,List> protocolHeaders =
(TreeMap<String, List>) message.get("org.apache.cxf.message.Message.PROTOCOL_HEADERS");
if (protocolHeaders != null) {
List value = protocolHeaders.get("SOAPAction");
String soapAction = value != null ? (String)value.get(0) : "";
if (soapAction.equalIgnoreCase(YOUR_SERVICE_SOAP_ACTION)) {
doNotLog=true;
}
}
super.handleMessage(message);
}
Now there is one more method you have to override in the same class.
#Override
protected String transform(String originalLogString) {
if (doNotLog) {
return null;
}
return originalLogString;
}
For all inbound responses, extend LoggingInInterceptor and only override transform method. You can just check the responseType from the originalLogString and ignore it.
Related
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
Question
Is there a way to globally handle Spring Messaging MessageDeliveryException caused by error (usualy insufficient authorities) in Spring WebSocket module?
Use case
I have implemented Spring WebSockets over STOMP to support ws connection in my webapp. To secure websocket endpoint I have created interceptor that authorizes user to start STOMP session at STOMP CONNECT time (as suggested in Spring documentation here in 22.4.11 section):
#Component
public class StompMessagingInterceptor extends ChannelInterceptorAdapter {
// Some code not important to the problem
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
switch (headerAccessor.getCommand()) {
// Authenticate STOMP session on CONNECT using jwt token passed as a STOMP login header - it's working great
case CONNECT:
authorizeStompSession(headerAccessor);
break;
}
// Returns processed message
return message;
}
// Another part of code not important for the problem
}
and included spring-security-messaging configuration to add some fine-grained control over authorities when messaging:
#Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.simpTypeMatchers(
SimpMessageType.CONNECT,
SimpMessageType.DISCONNECT,
SimpMessageType.HEARTBEAT
).authenticated()
.simpSubscribeDestMatchers("/queue/general").authenticated()
.simpSubscribeDestMatchers("/user/queue/priv").authenticated()
.simpDestMatchers("/app/general").authenticated()
.simpDestMatchers("/user/*/queue/priv").hasAuthority("ADMIN")
.anyMessage().denyAll();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
First of all - this configuration works as expected, the problem is when some security exception happens during websocket communication (say user without admin authority tries to send message on "/user/{something}/queue/priv" endpoint) it will end in org.springframework.messaging.MessageDeliveryException being rised and:
Full exception stack trace being written down to my server log
Returning STOMP ERROR frame containing part of stack trace as it's message field.
What I would like to do is catching (if possible globally) DeliveryException, checking what caused it and accoridingly to that create my own message for returning in STOMP ERROR frame (lets say with some error code like just 403 to mimic HTTP) and instead of throwing original exception further just logging some warning with my logger. Is it possible?
What I tried
When looking for solution I found some people using #MessageExceptionHandler to catch messaging exceptions, Spring 4.2.3 (which is version I use) documentation mentions it only once here in 25.4.11 section. I tried to use it like this:
#Controller
#ControllerAdvice
public class WebSocketGeneralController {
...
#MessageExceptionHandler
public WebSocketMessage handleException(org.springframework.messaging.MessageDeliveryException e) {
WebSocketMessage errorMessage = new WebSocketMessage();
errorMessage.setMessage(e.getClass().getName());
return errorMessage;
}
}
but it seems like method isn't called at any point (tried catching different exceptions, just Exception including - no results). What else should I look into?
#ControllerAdvice and #MessageExceptionHandler are working on business-logic level (like #MessageMapping or SimpMessagingTemplate).
To handle STOMP exceptions, you need to set STOMP error handler in STOMP registry:
#Configuration
#EnableWebSocketMessageBroker
class WebSocketConfiguration : WebSocketMessageBrokerConfigurer {
override fun configureMessageBroker(registry: MessageBrokerRegistry) {
// ...
}
override fun registerStompEndpoints(registry: StompEndpointRegistry) {
registry.addEndpoint("/ws")
// Handle exceptions in interceptors and Spring library itself.
// Will terminate a connection and send ERROR frame to the client.
registry.setErrorHandler(object : StompSubProtocolErrorHandler() {
override fun handleInternal(
errorHeaderAccessor: StompHeaderAccessor,
errorPayload: ByteArray,
cause: Throwable?,
clientHeaderAccessor: StompHeaderAccessor?
): Message<ByteArray> {
errorHeaderAccessor.message = null
val message = "..."
return MessageBuilder.createMessage(message.toByteArray(), errorHeaderAccessor.messageHeaders)
}
})
}
}
It does not work because of #ControllerAdvice catch exception from the request that passed dispatcher servlet. When you secure your endpoint and someone makes an unauthorized request it does not pass through dispatcher servlet. The request is caught by spring interceptors.
I am having a trouble with the Shiro Guice Filter ordering. The modules are registered like this:
#Override
protected Injector getInjector() {
Module[] modules = { new RestServiceModule(), // my services module
new MySecurityModule(context), // my wrapper around ShiroWebModule
new MyJerseyServletModule() }; // my wrapper around JerseyServletModule
logger.debug("Creating injector");
Injector injector = Guice.createInjector(modules);
return injector;
}
....
// MyJerseyServletModule
#Override
protected void configureServlets() {
// Binding rest entry points
bind(Hello.class);
bind(Anon.class);
// Setup GuiceContainer
serve("/*").with(GuiceContainer.class);
filter("/*").through(GuiceShiroFilter.class);
}
The problem is with the order filters are executed. GuiceShiroFilter is run before the Jersey JAX-RS filters (like #GET and #POST). In this case the request firstly trying to authenticate the user and after that validates the method (and send back "405 Method not allowed" for example).
I assume the transport protocol validation filters (Jersey filters in other words) should come in a first place. Am I missing something?
I want to do my own implementation of Bearer-only authentication in Wildfly. In essence, I will do the following steps:
When I receive a request, I will check if it has an Authorization header.
I obtain the token and check against a database (in this case I will be using Redis) for the validity of it.
I obtain the role for that user from the database.
I want to be able to use the #RolesAllowed annotation on my rest services.
How do I go about it? How do I need to modify the Wildfly configuration files? What interfaces do I need to implement? How can I pass the role of the user to the security context so that Wildfly does the #RolesAllowed check for me?
If answering, consider that I am an experienced Java Programmer, but new to Wildfly, so you can skip details on programming logic but not on Wildfly configuration. Also in your answer don't worry on how the token got to Redis in the first place, or how the client obtained it.
EDIT
This is what I have done, but with no luck yet. I have implemented an AuthenticationFilter that implements ContainerRequestFilter. (I am including below only the main filter function that I have implemented. Note that there are some helper functions that get the roles from the database that are not included). Even when, at the end of the function I set the security context of the request context with the user profile (which contains the role), I cannot get to work the #RolesAllowed annotations on my JAX-RS rest services. Any pointers on what should I do?
Note: I have not modified any Wildfly configuration files or the web.xml file. I know that the filter is being called for every request because I am able to LOG messages from it on every request.
/**
* (non-Javadoc)
* #see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext)
*/
#Override
public void filter(ContainerRequestContext requestContext) {
//1. Read the JSON web token from the header
String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
return;
}
String token = authorizationHeader.substring("Bearer".length()).trim();
try{
//Note that if the token is not in the database,
//an exception will be thrown and we abort.
UserProfile userProfile = this.getUserProfile(token);
if (null == userProfile){
userProfile = this.decodeToken(token);
}
if (null == userProfile){
throw new Exception();
}
String role = userProfile.getUserRole();
if (null == role){
role = this.getRoleFromMod(userProfile);
if (null == role){
role = RoleType.READ_ONLY;
}
userProfile.setUserRole(role);
this.updateUserProfileForToken(token, userProfile);
}
userProfile.setUserRole(role);
//5. Create a security context class that implements the crazy interface
//and set it here.
requestContext.setSecurityContext(new ModSecurityContext(userProfile));
}
catch(Exception e){
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
Yeah I am not sure how it would work in an EE environment, even making the resource class an stateless bean. The #RolesAllowed annotation is meant to be used for ejbs. In which case the principal is retrieved from the servlet request (I believe). What I would do is just implements your own authorization filter that looks up the annotation and checks against the principal in the security context.
You can see how Jersey implements it. Nothing is really Jersey specific about it except the AnnotatedMethod class. For that you can just do some reflection with java.lang.reflect.Method (resourceInfo.getResourceMethod()) instead. Other than that, you can pretty much copy the code as is. Once you're done, just register the RolesAllowedDynamicFeature with the application. Or just annotate it with #Provider to be scanned for.
You will also need to make sure your authentication filter is annotated with #Priority(Priorities.AUTHENTICATION) so that it is called before the authorization filter, which is annotated with #Priority(Priorities.AUTHORIZATION).
UPDATE
Here is a refactor of the the code I linked to, so It doesn't use an Jersey specific classes. The AnnotatedMethod is just changed to Method.
#Provider
public class RolesAllowedFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
Method resourceMethod = resourceInfo.getResourceMethod();
if (resourceMethod.isAnnotationPresent(DenyAll.class)) {
configuration.register(new RolesAllowedRequestFilter());
return;
}
RolesAllowed ra = resourceMethod.getAnnotation(RolesAllowed.class);
if (ra != null) {
configuration.register(new RolesAllowedRequestFilter(ra.value()));
return;
}
if (resourceMethod.isAnnotationPresent(PermitAll.class)) {
return;
}
ra = resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
if (ra != null) {
configuration.register(new RolesAllowedRequestFilter(ra.value()));
}
}
#Priority(Priorities.AUTHORIZATION) // authorization filter - should go after any authentication filters
private static class RolesAllowedRequestFilter implements ContainerRequestFilter {
private final boolean denyAll;
private final String[] rolesAllowed;
RolesAllowedRequestFilter() {
this.denyAll = true;
this.rolesAllowed = null;
}
RolesAllowedRequestFilter(final String[] rolesAllowed) {
this.denyAll = false;
this.rolesAllowed = (rolesAllowed != null) ? rolesAllowed : new String[]{};
}
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
if (!denyAll) {
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext)) {
throw new ForbiddenException("Not Authorized");
}
for (final String role : rolesAllowed) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
}
throw new ForbiddenException("Not Authorized");
}
private static boolean isAuthenticated(final ContainerRequestContext requestContext) {
return requestContext.getSecurityContext().getUserPrincipal() != null;
}
}
}
First let me explain a bit about how the DynamicFeature works. For that let's first change the context of discussion to your current implementation of your AuthenticationFilter.
Right now it is a filter that is processed for every request. But let's say we introduced a custom #Authenticated annotation
#Target({METHOD, TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Authenticated{}
We could use this annotation to annotate different methods and classes. To make it so that only the methods and classes annotated are filtered by the filter, we can introduce a DynamicFeature that checks for the annotation, then only register the filter when the annotation is found. For example
#Provider
public class AuthenticationDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext configuration) {
if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)) {
configuration.register(new AuthenticationFilter());
return;
}
if (resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
configuration.register(new AuthenticationFilter());
}
}
}
Once we register this AuthenticationDynamicFeature class, it will make it so that only methods and classes annotated with #Authenticated will be filtered.
Alternatively, this can even be done within the filter. We can get a reference to the ResourceInfo from within the AuthenticationFilter. For example check for the annotation, if is not there, then move on.
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Context
private ResourceInfo resourceInfo;
#Override
public void filter(ContainerRequestContext context) throws IOException {
boolean hasAnnotation = false;
if (resourceInfo.getResourceMethod().isAnnotationPresent(Authenticated.class)
|| resourceInfo.getResourceClass().isAnnotationPresent(Authenticated.class)) {
hasAnnotation = true;
}
if (!hasAnnotation) return;
// process authentication is annotation is present
This way we could completely forget about the DynamicFeature. It's better to just use the DynamicFeature, I was just giving an example for demonstration purposes.
But that being said, if we look at the first block of code with the RolesAllowedDynamicFeature, you can better understand what is going on. It only registers the filter for methods and classes annotated with #RolesAllowed and #DenyAll. You could even refactor it to have all the annotation logic in the filter instead of the feature. You only have the filter. Just like I did with the AuthenticationFilter example above. Again this would be just for example purposes.
Now as far as the registration of the DynamicFeature, it works the same way as registering any other resource class or provider class (e.g. your authentication filter). So however you register those, just register the RolesAllowedDynamicFeature the same way. There is scanning, where #Path and #Provider annotations are scanned for. If this is what you are current using, then just annotating the feature class with #Provider should register it. For example just having an empty Application subclass will cause scanning to happen
#ApplicationPath("/api")
public class RestApplication extends Application {}
Then there is explicit registration in your Application subclass. For example
#ApplicationPath("/api")
public class RestApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(AuthenticationFilter.class);
classes.add(RolesAllowedFeature.class);
classes.add(SomeResource.class);
return classes;
}
}
Note that when doing this, you disable any scanning that goes on.
So a couple other things to make sure after all the above is clear it still isn't working.
Make sure your current AuthenticationFilter is annotated with #Priority(Priorities.AUTHENTICATION). This is to ensure that your authentication filter is called before the authorization filter. This needs to happen because the authentication filter is what sets the security context, and the authorization filter checks it.
Make sure you are creating the security context correctly. The authorization filter will call the SecurityContext.isUserInRole(role) passing in roles from the #RolesAllowed annotation. So you need to make sure to implements the isUserInRole correctly.
I have an OSGi service which I have exposed with CXF as a simple web service and for which I have created a client which invokes its methods. The methods of this service accept as one of their arguments a signed ticket which identifies the client performing the request. In order to inject this ticket to all requests coming from my client I have created a CXF out interceptor, which I have bound to the SETUP phase, and which is responsible for the injection of the ticket in the outgoing message.
In case the ticket injected by my interceptor has expired, the service will throw an exception which I would like to be able to catch, get a fresh ticket and repeat the request with this fresh ticket in order to completely abstract the ticket-management functionality from the rest of my code. I have therefore created an in fault interceptor, which I have bound to the PRE_LOGICAL phase and in which I am able to identify whether the specific exception type I am interested in has been thrown. I am however unsure as to how I can repeat the request and return the result of the second request instead of the first one. Does CXF offer a way for me to do this?
Since I wasn't able to find a way to repeat the request through a fault interceptor I ended up using an InvocationHandler to allow me to control the request (effectively wrapping the proxy I get from CXF in another proxy). What I ended up with is something like the following:
ClientProxyFactoryBean factory = new ClientProxyFactoryBean();
// Configure factory
MyService serviceClient = (MyService) factory.create(MyService.class);
MyService proxy = (MyService) Proxy.newProxyInstance(
ServiceInvocationHandler.class.getClassLoader(),
new Class[] { MyService.class },
new ServiceInvocationHandler(serviceClient));
Where the ServiceInvocationHandler is:
public class ServiceInvocationHandler implements InvocationHandler {
private final Object proxied;
private SignedTicket ticket;
public ServiceInvocationHandler(Object proxied) {
this.proxied = proxied;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retVal = null;
try {
// Generate a ticket if the one held locally by this class is null
// and inject it in the method arguments
retVal = method.invoke(proxied, args);
} catch (Throwable t) {
if (t.getCause() instanceof InvalidTicketException) {
// Get a fresh ticket and inject it in the method arguments
retVal = method.invoke(proxied, args);
}
}
return retVal;
}
}