Spring: OAuth2 scheduled request after gotten information once - java

I am trying to request an object via oauth2 authentification with spring security and then try to use those credentials in my service, which is supposed to request data in fixed intervals.
The SleepController's purpose is to initially get data and as a side effect cache the important data for the service below.
#RestController
public class SleepController {
#Autowired
OAuth2RestTemplate oAuth2RestTemplate;
public static OAuth2RestTemplate currentOAuth2RestTemplate;
#RequestMapping("/sleep")
public OverallSleep getSleep(String date) {
if(date != null) {
OverallSleep overallSleep = null;
try {
overallSleep = oAuthRestTemplate.getForObject("https://api.fitbit.com/1.2/user/-/sleep/date/" + date + ".json", OverallSleep.class);
currentOAuth2RestTemplate = oAuth2RestTemplate;
} catch (Exception e) {
e.printStackTrace();
}
OverallSleep tmp = overallSleepDao.findOverallSleepByToday();
return overallSleep;
}
return null;
}
}
The BeatService has a scheduled function that tries to request data from the server without user interaction.
#Service
public class BeatService {
private static final long INTERVAL = 60_000L;
private static final long INITIAL_DELAY = 3_000L;
public BeatService() { }
#Scheduled(fixedRate = INTERVAL, initialDelay = INITIAL_DELAY)
public void beat() {
if(SleepController.currentOAuth2RestTemplate != null) {
OverallSleep overallSleep = null;
try {
overallSleep = SleepController.currentOAuth2RestTemplate.getForObject("https://api.fitbit.com/1.2/user/-/sleep/date/" + "2018-02-11" + ".json", OverallSleep.class);
overallSleepDao.save(overallSleep);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Sadly, my approach of statically storing the oauthRestTemplate and trying to use that in my scheduled service does not work.
This Exception is thrown
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'session' is not active for the current thread; consider defining a
scoped proxy for this bean if you intend to refer to it from a singleton;
nested exception is java.lang.IllegalStateException: No thread-bound request
found: Are you referring to request attributes outside of an actual web
request, or processing a request outside of the originally receiving thread?
If you are actually operating within a web request and still receive this
message, your code is probably running outside of
DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
at [...]
Caused by: java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of the
originally receiving thread? If you are actually operating within a web
request and still receive this message, your code is probably running
outside of DispatcherServlet/DispatcherPortlet: In this case, use
RequestContextListener or RequestContextFilter to expose the current
request.
Is there an appriopriate or right way to reuse the credentials from a prior authentification in oauth2 in Spring? I found a post sadly it doesn't work and actually make the controller crash so I can't test the service at all.
If needed, I can add other classes or what #Configuration I use.

Related

Spring Security: CookieTheftException with PersistentTokenBasedRememberMeServices

I have a web application with spring-boot 2.0.1 protected by spring-security. I use a PersistentTokenRepository for Remember-Me and store the tokens in a MySQL database.
In the server logfiles I see quite a lot of stacktraces with CookieTheftExceptions. It's so many that I find it hard to believe that actual Cookies are stolen but assume some kind of misconfiguration. From adding some analyzing code, it seems only mobile browsers are affected.
Servlet.service() for servlet [dispatcherServlet] in context with path [/r] threw exception
org.springframework.security.web.authentication.rememberme.CookieTheftException: Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
at org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices.processAutoLoginCookie(PersistentTokenBasedRememberMeServices.java:119) ~[spring-security-web-5.0.4.RELEASE.jar!/:5.0.4.RELEASE]
Manual testing was not able to reproduce this. Deleting the session cookie, but keeping the remember-me Cookie and making a request to a restricted URL leads to a normal authenticated session.
Here are the relevant parts of my security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Configuration
public static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private RememberMeServices rememberMeServices;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.rememberMe()
.key(rememberMeKey)
.rememberMeServices(rememberMeServices);
;
}
}
/**
* Key for RememberMeServices and RememberMeAuthenticationProvider.
*/
private static final String rememberMeKey = "...";
#Bean
public RememberMeServices rememberMeServices(UserDetailsService userDetailsService, PersistentTokenRepository persistentTokenRepository) {
PersistentTokenBasedRememberMeServices rememberMeServices = new AnalyzingPersistentTokenBasedRememberMeServices(
rememberMeKey, userDetailsService, persistentTokenRepository);
rememberMeServices.setTokenValiditySeconds((int) Duration.of(366L, ChronoUnit.DAYS).toSeconds());
return rememberMeServices;
}
#Bean
public PersistentTokenRepository persistentTokenRepository(JdbcTemplate jdbcTemplate) {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setJdbcTemplate(jdbcTemplate);
return tokenRepository;
}
}
That AnalyzingPersistentTokenBasedRememberMeServices is a PersistentTokenBasedRememberMeServices with some additional logging in processAutoLoginCookie.
Another speciality is, that I use a custom AuthenticationProvider, and provide a UserDetailsService only for RememberMe. But as said above, manual testing works just fine. Still, users report they get logged out too often (session timeout is 24 hours).
Did anybody experience something like this and has a solution? Do I miss some crucial configuration?
PersistentTokenBasedRememberMeServices is not suitable for applications with concurrent requests, which might send the same token series.
See these almost five year old and unresolved bug reports:
https://github.com/spring-projects/spring-security/issues/2648
https://github.com/spring-projects/spring-security/issues/3079
Using TokenBasedRememberMeServices does not have those problems.
I ended up using Redisson to implement distributed locking and a distributed map to hold a fast-expiring cache, enabling the first-arriving request of a batch of concurrent requests to renew the token and allowing the soon-to-follow requests to be aware that the token had recently refreshed and use that new value.
In spring security web 4.2.13 implementation problem in mechanism of token prolongation
protected UserDetails processAutoLoginCookie(String[] cookieTokens,...
.....
PersistentRememberMeToken newToken = new PersistentRememberMeToken(
token.getUsername(), token.getSeries(), generateTokenData(), new Date());
try {
tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
newToken.getDate());
addCookie(newToken, request, response);
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
During refreshing in case of concurrent request token value for same series can be rewritten in DB few times, but in cookie can be saved another value.
The token creation during login works well, in worst case u can have many persisted tokens in DB, but one of them will work =)
In my project I have fix bug by modification of refresh algorithm:
if (token.date + 1 < new Date()){
try {
PersistentRememberMeToken newToken = new PersistentRememberMeToken( token.getUsername(), generateSeriesData(), generateTokenData(), new Date())
tokenRepository.createNewToken(newToken)
addCookie(newToken, request, response)
}
catch (Exception e) {
logger.error("Failed to update token: ", e);
throw new RememberMeAuthenticationException(
"Autologin failed due to data access problem");
}
}
This code produce many DB records at result, but works

Java Websocket / MessageHandler return to global scope?

I'm facing the following problem and I found no working solution yet.
I have 3 different applications that should communicate with each other:
the UI part (1)
the backend application (2)
the microservice "in the cloud" (3)
The backend application provides a Webservice (REST) for the UI to get and put information from/to the microservice.
Everything I want to grab from the microservice works fine, but:
If I want to put data to the microservice, the specs require a websocket connection. This works fine too, but the microservice returns a message after the (un-)successful command, like
{"statusCode":200,"messageId":"1234567890"}
The problem now is: How can I grab this message in my application and send it back to the UI, so the user knows if the command was successful?
For the moment I tried this:
WebSocketClient.java
#OnMessage
public void onMessage(Session session, String msg) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(msg);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public static interface MessageHandler {
public String handleMessage(String message);
}
MyTotalAwesomeController.java
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
...
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
...
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
return message;
}
});
return ResponseEntity.ok("worked");
}
I can see the console output from the MessageHandler return, but I don't know how I can pass this to the parent method for return insted of just returning the ResponseEntity.ok().
I'm not very used to WebSocket connections in Java yet, so please don't judge me ;-)
Thank you for your help.
The code below will work under the assumption that the #OnMessage method is executed in a thread managed by the WebSocket client runtime. Please inspect the thread that runs the #OnMessage method.
If the above premise is true, the putDataToMicroservice() method, executed by a thread in the global scope, will wait until the WebSocket response arrives at the WS client thread, which will repass the message to the global scope thread. Then the execution in your controller class will continue.
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
// Queue for communication between threads.
private BlockingQueue<String> queue;
#PostConstruct
void init() {
queue = new SynchronousQueue<>(true);
// This callback will be invoked by the WebSocket thread.
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
#Override
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
// Pass message to the controller thread.
queue.put(message);
// Note that the return value is not necessary.
// You can take it out of the interface as well.
return null;
}
});
}
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
// At this point you make a WebSocket request, is that right?
doWebSocketRequest();
// This poll call will block the current thread
// until the WebSocket server responds,
// or gives up waiting after the specified timeout.
//
// When the WebSocket server delivers a response,
// the WS client implementation will execute the
// #OnMessage annotated method in a thread
// managed by the WS client itself.
//
// The #OnMessage method will pass the message
// to this thread in the queue below.
String message = queue.poll(30, TimeUnit.SECONDS);
if (message == null) {
// WebSocket timeout.
}
return ResponseEntity.ok("worked");
}
}

Overriding zuul error filter results in Forwarding error

I'm trying to override the default senderorfilter for zuul proxy so that I can present a better response when a service behind my gateway has crashed/is down. Here is the code that I took from the answer in Customizing Zuul Exception
public class RabbrErrorFilter extends ZuulFilter {
private static final Logger log = LoggerFactory.getLogger(RabbrErrorFilter.class);
protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return Integer.MIN_VALUE; // Needs to run before SendErrorFilter which has filterOrder == 0
}
#Override
public boolean shouldFilter() {
// only forward to errorPath if it hasn't been forwarded to already
RequestContext ctx = RequestContext.getCurrentContext();
return ctx.containsKey("error.status_code") && !ctx.getBoolean(SEND_ERROR_FILTER_RAN,false);
}
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException)e;
log.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
// Populate context with new response values
ctx.setResponseBody("Overriding Zuul Exception Body");
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); //Can set any error code as excepted
ctx.setSendZuulResponse(false);
}
}
catch (Exception ex) {
log.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
I've checked with a debugger that the code in run() is executed when a zuul exception is thrown, but in the console log I get this:
com.netflix.zuul.exception.ZuulException: Forwarding error
at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.handleException(RibbonRoutingFilter.java:157) ~[spring-cloud-netflix-core-1.1.0.RC2.jar:1.1.0.RC2]
at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.forward(RibbonRoutingFilter.java:132) ~[spring-cloud-netflix-core-1.1.0.RC2.jar:1.1.0.RC2]
at org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter.run(RibbonRoutingFilter.java:78) ~[spring-cloud-netflix-core-1.1.0.RC2.jar:1.1.0.RC2]
at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112) ~[zuul-core-1.1.0.jar:1.1.0]
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:197) ~[zuul-core-1.1.0.jar:1.1.0]
at com.netflix.zuul.FilterProcessor.runFilters(FilterProcessor.java:161) ~[zuul-core-1.1.0.jar:1.1.0]
at com.netflix.zuul.FilterProcessor.route(FilterProcessor.java:120) ~[zuul-core-1.1.0.jar:1.1.0]
at com.netflix.zuul.ZuulRunner.route(ZuulRunner.java:96) ~[zuul-core-1.1.0.jar:1.1.0]
at com.netflix.zuul.http.ZuulServlet.route(ZuulServlet.java:116) ~[zuul-core-1.1.0.jar:1.1.0]
at com.netflix.zuul.http.ZuulServlet.service(ZuulServlet.java:81) ~[zuul-core-1.1.0.jar:1.1.0]
and the response from the service behind the gateway is timed out with status 500.
-- Edit*
Updating the spring-cloud dependencies (contains zuul 1.3.0 core) did not fix this
Please check this solution: Spring Cloud: Zuul error handling. It worked for me, specially for Forwarding error of Zuul.
The Zuul error handling is being handled through RibbonRoutingFilter and SendErrorFilter and will forward any errored request to ${error.path}, which defaults to ‘/error’. In case you relay on the defaults that will be handled by Spring Boot’s BasicErrorController. You can overide this behaviour and implement your own ErrorController.
You don't necessarily need to follow a Vnd error style, just follow the general logic of coding a custom implementation of Spring's ErrorController.

Spring Integration application with external Web Services monitoring

Currently I have an application with Spring Integration DSL that has AMQP inbound gateway with different service activators, each service activator has kind of logic to decide, transform and call external web services (currently with CXF), but all this logic is in code without Spring Integration components.
These service activators are monitored, in the output channel that returns data from this application is an AMQP adapter that sends headers to a queue (after that, all headers are processed and saved in a database for future analysis). This works well, these service activators even have elapsed time in headers.
Now, the problem is, that I need to monitor external web service calls, like elapsed time in each operation, which service endpoint and operation was called, if an error occurred.
I've been thinking that logic code in each service activator should be converted into a Spring Integration flow, in each service activator, would call a new gateway with the name of the operation of the web service in a header, and monitoring every flow as currently I had been doing.
So, I'm not sure if this manual approach is the better approach, so I wonder if there is a way to get the name of the service operation with some kind of interceptor or something similar with CXF or Spring WS to avoid setting the name of the operation in headers in a manual way? What would be your recommendation?
To have more context here is the Spring Integration configuration:
#Bean
public IntegrationFlow inboundFlow() {
return IntegrationFlows.from(Amqp.inboundGateway(simpleMessageListenerContainer())
.mappedReplyHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.mappedRequestHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.errorChannel(gatewayErrorChannel())
.requestChannel(gatewayRequestChannel())
.replyChannel(gatewayResponseChannel())
)
.enrichHeaders(new Consumer<HeaderEnricherSpec>() {
#Override
public void accept(HeaderEnricherSpec t) {
t.headerExpression(AMQPConstants.START_TIMESTAMP, "T(java.lang.System).currentTimeMillis()");
}
})
.transform(getCustomFromJsonTransformer())
.route(new HeaderValueRouter(AMQPConstants.OPERATION_ROUTING_KEY))
.get();
}
#Bean
public MessageChannel gatewayRequestChannel() {
return MessageChannels.publishSubscribe().get();
}
#Bean
public MessageChannel gatewayResponseChannel() {
return MessageChannels.publishSubscribe().get();
}
private IntegrationFlow loggerOutboundFlowTemplate(MessageChannel fromMessageChannel) {
return IntegrationFlows.from(fromMessageChannel)
.handle(Amqp.outboundAdapter(new RabbitTemplate(getConnectionFactory()))
.exchangeName(LOGGER_EXCHANGE_NAME)
.routingKey(LOGGER_EXCHANGE_ROUTING_KEY)
.mappedRequestHeaders("*"))
.get();
}
And here is a typical service activator, as you can see, all this logic could be an integration flow:
#ServiceActivator(inputChannel="myServiceActivator", outputChannel = ConfigurationBase.MAP_RESPONSE_CHANNEL_NAME)
public Message<Map<String, Object>> myServiceActivator(Map<String, Object> input, #Header(AMQPConstants.SESSION) UserSession session) throws MyException {
Message<Map<String, Object>> result = null;
Map<String, Object> mapReturn = null;
ExternalService port = serviceConnection.getExternalService();
try {
if (input.containsKey(MappingConstants.TYPE)) {
Request request = transformer
.transformRequest(input, session);
Response response = port
.getSomething(request);
utils.processBackendCommonErrors(response.getCode(), response.getResponse());
mapReturn = transformer.convertToMap(response);
} else {
Request request = transformer
.transformRequest(input, session);
Response response = port
.getSomethingElse(request);
utils.processBackendCommonErrors(response.getCode(),
response.getResponse());
mapReturn = transformer.convertToMap(response);
}
} catch (RuntimeException e) {
String message = "unexcepted exception from the back-end";
logger.warn(message, e);
throw MyException.generateTechnicalException(message, null, e);
}
result = MessageBuilder.withPayload(mapReturn)
.build();
return result;
}
So far so good. Or I don't understand the problem, or you are not clear where it is.
Anyway you always can proxy any Spring Service with the AOP, since it looks like you are pointing to the code:
Response response = port
.getSomething(request);
When this (or similar) method is called, some MethodInterceptor can perform desired tracing logic and send result to some MessageChannel for further analysis or anything else to do:
public Object invoke(MethodInvocation invocation) throws Throwable {
// Extract required operation name and start_date from the MethodInvocation
Object result = invocation.proceed();
// Extract required data from the response
// Build message and send to the channel
return result;
}

Can I wrap all JAX-RS requests with custom pre-dispatch, post-dispatch and error-handler code?

I have a number of classes exposed as JAX-RS request "handlers", using javax.ws.rs.Path annotations. I want to add certain actions before every request and after each request. Also, I need to create a global application-wide exception handler, which will catch everything thrown by these handlers and protocol.
Is it possible to achieve this with standard JAX-RS without creating of a custom class inherited from com.sun.jersey.spi.container.servlet.ServletContainer (I'm using Jersey).
You can also use ExceptionMappers. This mechanism which catch the exception thrown by your service and convert it to the appropriate Response:
#Provider
public class PersistenceMapper implements ExceptionMapper<PersistenceException> {
#Override
public Response toResponse(PersistenceException arg0) {
if(arg0.getCause() instanceof InvalidDataException) {
return Response.status(Response.Status.BAD_REQUEST).build();
} else {
...
}
}
}
For more information see:
JAX-RS using exception mappers
You could create a proxy RESTful service and use this as the entry point to all your other RESTful services. This proxy can receive requests, do any pre-processing, call the RESTful service required, process the response and then return something to the caller.
I have a set up like this in a project I've been working on. The proxy performs functions like authentication, authorisation and audit logging. I can go into further details if you like.
Edit:
Here is an idea of how you might want to implement a proxy that supports GET requests;
#Path("/proxy")
public class Proxy
{
private Logger log = Logger.getLogger(Proxy.class);
#Context private UriInfo uriInfo;
#GET
#Path("/{webService}/{method}")
public Response doProxy(#Context HttpServletRequest req,
#PathParam("webService") String webService,
#PathParam("method") String method)
{
log.debug("log request details");
//implement this method to work out the URL of your end service
String url = constructURL(req, uriInfo, webService, method);
//Do any actions here before calling the end service
Client client = Client.create();
WebResource resource = client.resource(url);
try
{
ClientResponse response = resource.get(ClientResponse.class);
int status = response.getStatus();
String responseData = response.getEntity(String.class);
log.debug("log response details");
//Do any actions here after getting the response from the end service,
//but before you send the response back to the caller.
return Response.status(status).entity(responseData).build();
}
catch (Throwable t)
{
//Global exception handler here
//remember to return a Response of some kind.
}
}
You can use filters to read and modify all requests and responses.

Categories