Spring Rest Set Custom Object in Context - java

I am using Spring Rest for creating Rest APIs. For authentication, I have created a filter that extends from OncePerRequestFilter, this filter check if a valid token is present in the header. I have to set some custom object information in Spring context. So that I can retrieve it in my Controller classes. Something like:
AuthenticationFilter extends OncePerRequestFilter {
// validation goes here
requestContext.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return new UserInfo("", token, userID, userType);
}
}
});

Don't create custom filters for authentications. User security context instead it's pretty powerful.
I don't know about any option how put custom object into spring context. But I can show you how to create your custom context in spring framework and then use it as any other context. Of course you will be able to put custom objects into it.
PSEUDO CODE:
1.)
Create custom object which you want to have in context:
public class UserInfo implements Serializable {
private String id;
private String email;
//Getters and setters
}
2.)
Create custom context:
#Service
public class UserInfoContext {
private static final ThreadLocal<UserInfo> userInfoThreadLocal = new InheritableThreadLocal<>();
public void setUserInfo(UserInfo userInfo) {
UserInfoContext.userInfoThreadLocal.set(userInfo);
}
public UserInfo getUserInfo() {
return UserInfoContext.userInfoThreadLocal.get();
}
public void clearContext() {
UserInfoContext.userInfoThreadLocal.remove();
}
}
3.)
Create custom interceptor for initializing custom object inside custom context
#Service
public class UserInfoInterceptor implements HandlerInterceptor {
private UserInfoContext userInfoContext;
#Autowired
public UserInfoInterceptor(UserInfoContext userInfoContext) {
this.userInfoContext = userInfoContext;
}
#Override
public boolean preHandle(HttpServletRequest requestServlet, HttpServletResponse responseServlet, Object handler) throws Exception {
// Call userInfoContext.setUserInfo() with custom data.
return true;
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
userInfoContext.clearContext();
}
}
Thanks to ThreadLocal you are able to save custom information about current user.

Related

Creating and injecting a per request scoped variable

I would like to have a variable that follows along the full lifecycle of a request in java EE.
For example it could be for a logging function, so that I can filter all log entries by request.
The key part that I want to get at is that it must be relatively easy to implement in an already existing application so if possible some sort of dependency injection that gets the variable related to the specific request.
I've tried injectiong a #RequestScoped variable, but it doesn't work since it is only scoped to the container. I would need to be able to inject the same object to different containers. Is this at all possible?
EDIT: I want something along the lines of this:
#RequestScoped
public class RequestVariables {
public String id;
}
#Stateless
public class Logger {
#Inject
private RequestVariables requestVariables;
public void log(String message) {
System.out.println(requestVariables.id + ":" + message);
}
}
#Stateless
public class Service {
#Inject
private Logger logger;
#Inject
private RequestVariables requestVariables;
public void save(String data) {
logger.log("Save");
session.save(data + requestVariables.id); //Maybe add request parameter to save aswell
}
}
public class API {
#Inject
private Service service;
#Inject
private Logger logger;
#Inject
private RequestVariables requestVariables;
#Path("/1")
#GET
public Response get(#QueryParam("data") String data) {
requestVariables.id = UUID.randomUUID().toString()
service.save(data);
logger.log("Get");
return Response.status(204).build();
}
}
Currently this is what I have experimented with:
#RequestScoped
public class RequestScope {
private int test = 0;
public RequestScope(int test) {
this.test = test;
}
public RequestScope(){}
public int getTest() {
return test;
}
public void setTest(int test) {
this.test = test;
}
}
#Provider
public class RequestScopeFilter implements ContainerRequestFilter {
#Inject
private javax.inject.Provider<RequestScope> requestScopeProvider;
#Context
private HttpServletRequest request;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
requestScopeProvider.get().setTest(42);
request.setAttribute("test", "superTest");
}
}
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
#TransactionAttribute(value=TransactionAttributeType.NOT_SUPPORTED)
public class Service {
#Context
private HttpServletRequest httpServletRequest;
#Inject
private Provider<RequestScope> requestScopeProvider;
public void test() {
RequestScope scope = requestScopeProvider.get();
String test = (String)httpServletRequest.getAttribute("test");
}
}
So when I get the scope from my service then it is a new object with test set to 0, and then it throws an NPE since httpServletRequest is null
option #1
Implement an Interceptor and set the request id as HttpServletRequest attribute:
#AroundInvoke
public Object setRequestId(InvocationContext ic) throws Exception {
HttpServletRequest request = [..] // getHttpServletRequest(ic);
request.setAttribute("request-id", UUID.randomUUID().toString());
return ic.proceed();
}
Then use HttpServletRequest everywhere you need it
#Context
private HttpServletRequest httpRequest;
option #2
If want just to filter your logs by an unique id, you can configure your Logger to print the thread name: [%t]
Example: Log4j PatternLayout
option #3
Use a custom java bean to encapsulate the request data (query param, request id etc.) and pass this bean across your application services.
public class API {
#Inject
private Service service;
#Path("/1")
#GET
public Response get(MyCustomRequestBean data) {
service.doSomejob(data);
return Response.status(204).build();
}
}
Set the request id and query param in ParamConverter:
Jax-RS ParamConverter - ParamConverterProvider method return type mismatch
You can inject a provider in your service:
#Inject
Provider<RequestVariables> vars
And then call get () to get the instance. If you try to get () in a thread outside a request scope context you'll get an exception. I would however try to structure in a way that would not allow this to happen
A solution that I found is to use ThreadLocal variables. It seems rather dirty, but it works since each request is executed on it's own thread(as far as I am aware). So this is what I got:
public class RequestScope {
private static final ThreadLocal<String> id = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
public static String get() {
return id.get();
}
}
With that I can also easily exchange the ThreadLocal to return something more specific if so desired.
And I can get the variables from pretty much anywhere, assuming that the request is not starting a different thread

SPRING #RestController: Passing Cookie to a Service from Every Method

I have a #RestController in which every method needs to pass a (WebSSO) cookie down to a service. The service in turn uses the cookie for authentication. I am autowiring the service bean in controller. The service has a setter setCredentials(String webSSOCookie). One easy way is to call this setter in every method. I would like to do it better way; for instance using HandlerInterceptor. However the HandlerInterceptor does not have access to the controller (and hence its members) - am I right?
In jersey I could use filter. How do I achieve it in SPRING?
#RestController
#RequestMapping("/documents")
public class ECMRestController {
#Autowired
public ECMService ecmService;
#RequestMapping(value="/{documentId}", method=RequestMethod.DELETE)
public void deleteDocument(#RequestParam("documentId") String documentId) throws IllegalArgumentException, HttpClientErrorException {
// I could get and pass the cookie to ecmService in every method.
// ecmService.setCredentials(webSSOCookieObtainedfromRequest);
// However I don't want to do it that way.
ecmService.deleteDocument(documentId);
}
// Other REST Methods that need to pass the cookie in the same way.
}
You can request the SecurityContextHolder to query the current Authentication that you had customized in a filter.
MyCustomAuth auth = (MyCustomAuth) SecurityContextHolder.getContext().getAuthentication();
auth.getCookie();
or you can just use ThreadLocal in a context that can be retrieve from anywhere:
public class CookieContext {
private static final ThreadLocal<Cookie> COOKIE = new ThreadLocal<>();
private static final CookieContext INSTANCE = new CookieContext();
public void setCookie(Cookie value) {
COOKIE.set(value);
}
public Cookie getCookie() {
return COOKIE.get();
}
public static CookieContext getContext() {
return INSTANCE;
}
}
public class CookieInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
CookieContext context = CookieContext.getContext();
context.setCookie(request.getCookies()[0]);
}
}

How to Optionally Protect a Resource with Custom Dropwizard Filter

I'm using Dropwizard 0.9.2 and I want to create a resource that requires no authentication for GET and requires basic authentication for POST.
I have tried
#Path("/protectedPing")
#Produces(MediaType.TEXT_PLAIN)
public class ProtectedPing {
#GET
public String everybody() {
return "pingpong";
}
#PermitAll
#POST
public String authenticated(){
return "secret pingpong";
}
with
CachingAuthenticator<BasicCredentials, User> ca = new CachingAuthenticator<>(environment.metrics(), ldapAuthenticator, cbSpec);
AdminAuthorizer authorizer = new AdminAuthorizer();
BasicCredentialAuthFilter<User> bcaf = new BasicCredentialAuthFilter.Builder<User>().setAuthenticator(ca).setRealm("test-oauth").setAuthorizer(authorizer).buildAuthFilter();
environment.jersey().register(bcaf);
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
environment.jersey().register(new ProtectedPing());
This seems to result in all requests to "/protectedPing" requiring basic auth.
In Dropwizard 0.9.2 the documentation says to create a custom filter if I have a resource that is optionally protected. I'm assuming I need to do that, but I don't know where to start, or if that I what I actually need to do.
this is more of a jersey problem than a dropwizard problem. You can have a look here: https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Essentially what you want is:
Create an annotation that indicates that you want to test for authentication (e.g. #AuthenticatePost)
Create the resource and annotate the correct method with #AuthenticatePost
Create your authentication filter (probably kind of like what you did above).
In the dynamic feature, test for the annotation to be present on the passed in resource. This will hold true for post, false for get. Then register the AuthenticationFilter directly on the resource method instead of globally on the resource.
This would be a semi-complete example of how I would solve this:
public class MyDynamicFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
if(resourceInfo.getResourceMethod().getAnnotation(AuthenticateMe.class) != null ) {
context.register(MyAuthFilter.class);
}
}
public class MyAuthFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// do authentication here
}
}
public #interface AuthenticateMe {
}
#Path("myPath")
public class MyResource {
#GET
public String get() {
return "get-method";
}
#POST
#AuthenticateMe
public String post() {
return "post-method";
}
}
}
Note, the DynamicFeature checks that the Authenticate Annotation is present, before registering the authentication with the feature context.
I hope that helps,
let me know if you have any questions.

Custom provider can't inject into filter

I have a POJO that I'd like to inject into resources and filters:
public final class MyObject { }
I implemented a custom provider for it:
#Provider
public final class MyProvider
extends AbstractHttpContextInjectable<MyObject>
implements InjectableProvider<Context, Type> {
#Context private HttpServletRequest request;
#Override
public Injectable<MyObject> getInjectable(
ComponentContext componentContext,
Context annotation,
Type type
) {
if (type.equals(MyObject.class)) {
return this;
}
return null;
}
#Override
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
#Override
public MyObject getValue(HttpContext httpContext) {
//in reality, use session info from injected request to create MyObject
return new MyObject();
}
}
The object is successfully injected into my resource:
#Path("/test")
#ResourceFilters(MyFilter.class)
public final class MyResource {
#Context private HttpServletRequest request;
#Context private MyObject myObject;
#GET
public String execute() {
System.out.println(request != null); //true
System.out.println(myObject != null); //true
return "data";
}
}
But Jersey fails to inject it into my filter:
public final class MyFilter implements ResourceFilter {
#Context private HttpServletRequest request;
#Context private MyObject myObject;
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
System.out.println(request != null); //true
System.out.println(myObject != null); //false
return containerRequest;
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
}
I'm guessing the difference has to do with the fact that in MyFilter the injection is done using proxies that defer to thread-local instances - this is because the fields annotated with #Context are declared in the outer class, which is instantiated once, but they are used to inject objects on a per-request basis. When I step through filter during debugging, I can see that MyFilter.request points to a proxy wrapping an instance of com.sun.jersey.server.impl.container.servlet.ThreadLocalInvoker.
What is my custom provider (or implementation otherwise) missing that it needs to do custom injection into my filter?
Note that I'm currently stuck with Jersey 1.1.4.1 (sorry).
EDIT: Using Jersey 1.17, I get an exception at startup instead:
SEVERE: Missing dependency for field: private mypackage.MyObject mypackage.MyFilter.myObject
I found a workaround using the Providers injectable interface from JSR-311. First, I had to make my provider implement ContextResolver:
#Provider
public final class MyProvider
extends AbstractHttpContextInjectable<MyObject>
implements InjectableProvider<Context, Type>, ContextResolver<MyObject> {
...
#Override
public MyObject getContext(Class<?> type) {
//in reality, using the same logic as before
return new MyObject();
}
}
Then I injected a Providers instance into my filter instead. When filter is called, I use it to look up the ContextResolver for MyObject and retrieve it dynamically:
public final class MyFilter implements ResourceFilter {
#Context private HttpServletRequest request;
#Context private Providers providers;
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
final ContextResolver<MyObject> myObjectResolver =
providers.getContextResolver(MyObject.class, null);
final MyObject myObject =
myObjectResolver.getContext(MyObject.class);
System.out.println(request != null); //true
System.out.println(myObject != null); //true
return containerRequest;
}
};
}
...
}
Credit goes to this answer for tipping me off about Providers. The solution works, but it isn't a pretty one. I'd still like to inject MyObject anywhere and just have it work, like HttpServletRequest - and I'd like to know what it is my provider's missing that it needs to make that happen.
I came across this question trying to achieve the same goal (i.e. inject something provided by a custom Provider implementation) and found that custom injection works nicely if you go by way of a filter factory.
So instead of putting the #Context MyClass myObj annotation into the Filter class, place an annotated field of that type in the filter factory responsible for creating the filter and have the factory pass "myObj" as a regular parameter.
I'm not sure this will help in your case and I haven't investigated the implications of using this with a per-request provider (mine is singleton-scoped), so YMMV.

Controller library direct access to HttpServletRequest

I have numerous controllers in my application that extend a central controller class. Currently, in every controller function, I have to pass the request into this function in order for my function to grab the current username. Is it possible for this controller class to get the request on it's own without requiring it as an extra parameter?
public class Controller {
protected String environment;
public Controller () {
}
public ModelAndView prepareModel (HttpServletRequest request, ModelAndView model) {
contentDao.clearExpiredLocks();
model.addObject("currentUser", contentDao.findUser(request.getRemoteUser()));
//define current environment
this.environment = (request.getRequestURL().indexOf("localhost") > 0) ? "dev" : "uat";
model.addObject("environment", this.environment);
You can get the current HttpServletRequest as follows:
HttpServletRequest request = (HttpServletRequest) RequestContextHolder
.currentRequestAttributes()
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
You can use this code in a method of your controller, or use it to expose request as a request-scoped bean and inject the corresponding scoped proxy as a field of your controller.
You can use something like this:
public abstract class AbstractController {
protected HttpServletRequest req
protected AbstractController(HttpServletRequest req) {
this.req = req
}
}
public class ConcreteController extends AbstractController {
protected ConcreteController(String name) {
super(name);
}
private void getUserName(){
this.req.getRemoteUser();
}
}
That's just one quick tip, I believe that there are more possibilities how to do that.
In my case, What I did is : I get user MainController.getLoginPerson() and use all user's info in all controllers. All controllers extends to MainController.
Here is method MainController.getLoginPerson():
MainController.getLoginPerson() {
// calls to authentication service method
authenticationService.getLoginPerson();
}
authenticationService.getLoginPerson() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return (UserPrincipalImpl) auth.getPrincipal();
} else {
return null;
}
}

Categories