I faced with the following issue while using Spring MVC with ThreadLocal and WebClient from Webflux.
My task is to:
Intercept the user's request to my application and get all the headers from it and save it in ThreadLocal.
After that, when my application makes a call to another service through the WebClient, intercept this request in ExchangeFilterFunction and supplement it with the Authorization header from p.1.
When I finish processing the user's request, I clear the context.
I use my custom class "RequestContext" to store headers in ThreadLocal:
public class RequestContext {
private HttpHeaders requestHeaders;
private String jwt;
private static final String BEARER_PREFIX = "Bearer ";
public RequestContext(HttpHeaders httpHeaders) {
this.requestHeaders = httpHeaders;
if (Objects.nonNull(httpHeaders)) {
init();
}
}
private void init() {
if (Objects.nonNull(requestHeaders)) {
extractJwt();
}
}
private void extractJwt() {
var jwtHeader = requestHeaders.getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.isNotBlank(jwtHeader) && jwtHeader.startsWith(BEARER_PREFIX)) {
jwt = jwtHeader.substring(7);
}
}
}
I use my custom clas "RequestContextService" to deal with ThreadLocal:
public class RequestContextService {
private static final ThreadLocal<RequestContext> CONTEXT = new InheritableThreadLocal<>();
public void init(RequestContext requestContext) {
if (Objects.isNull(CONTEXT.get())) {
CONTEXT.set(requestContext);
} else {
log.error("#init: Context init error");
}
}
public RequestContext get() {
return CONTEXT.get();
}
public void clear() {
CONTEXT.remove();
}
}
My app is a WebMvc app. To complete step 1, I intercept the request with an HandlerInterceptor and set all headers to Threadlocal.
public class HeaderInterceptor implements HandlerInterceptor {
private final RequestContextService requestContextService;
#Override
public boolean preHandle(#NonNull HttpServletRequest request,
#NonNull HttpServletResponse response,
#NonNull Object handler) {
if (Objects.equals(request.getDispatcherType(), DispatcherType.REQUEST)) {
var headers = new ServletServerHttpRequest(request).getHeaders();
requestContextService.init(new RequestContext(headers));
}
return true;
}
#Override
public void afterCompletion(#NonNull HttpServletRequest request, #NonNull HttpServletResponse response,
#NonNull Object handler, Exception ex) {
requestContextService.clear();
}
}
As you can see, after every request I call "requestContextService.clear()" method to clear ThreadLocal.
To perform step two, I use the ExchangeFilterFunction, where I turn to the threadlocal and get the title from there.
public class SamlExchangeFilterFunction implements ExchangeFilterFunction {
private final RequestContextService requestContextService;
private static final ClientResponse UNAUTHORIZED_CLIENT_RESPONSE =
ClientResponse.create(HttpStatus.UNAUTHORIZED).build();
#Override
public #NotNull Mono<ClientResponse> filter(#NotNull ClientRequest request, #NotNull ExchangeFunction next) {
var jwt = requestContextService.get().getJwt();
if (StringUtils.isNoneBlank(jwt)) {
var clientRequest = ClientRequest.from(request)
.headers(httpHeaders -> httpHeaders.set(SAML_HEADER_NAME, jwt))
.build();
return next.exchange(clientRequest);
}
return Mono.just(UNAUTHORIZED_CLIENT_RESPONSE);
}
}
The problem is that the SamlExchangeFilterFunction works correctly only once.
On the first request to the application, everything works as it should. But with further requests with different authorization headers, the ExchangeFilterFunction seems to cache the value from the first request and substitutes it despite the fact that the threadlocal itself contains a completely different meaning of Authorization header.
Related
I am new to spring boot recently i am trying to do HTTP POST request from one android application through retrofit2 rest API library with application/x-www-form-url encoded but when i hit my spring boot POST service it shows me below error
"status":415,"error":"Unsupported Media
Type","exception":"org.springframework.web.HttpMediaTypeNotSupportedException","message":"Content
type 'application/x-www-form-urlencoded;charset=UTF-8' not
supported","path":"/api/login"
Can anyone know how to solve it?
Here is my code Android Code example
ApiService.java
public interface ApiService {
#FormUrlEncoded
#POST("/api/login")
Call<LoginData> postLogIn(
#Field("username") String username,
#Field("password") String password);
}
ApiHandler.java
private static final String SERVER_URL = "http://192.168.0.12:8080/";
private static final long CONNECTION_TIMEOUT = 30;
public static Retrofit restAdapter;
//public static final int CONNECTION_TIME_OUT = 120;
private static Retrofit getRestAdapter() {
if (restAdapter == null) {
restAdapter = new Retrofit.Builder()
.baseUrl(SERVER_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(getClient()).build();
}
return restAdapter;
}
private static OkHttpClient getClient() {
OkHttpClient.Builder okClientBuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okClientBuilder.addInterceptor(httpLoggingInterceptor);
okClientBuilder.connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS);
okClientBuilder.readTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS);
okClientBuilder.writeTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS);
return okClientBuilder.build();
}
PostHandler.java
#Override
public void callAPI(final Context context, final ApiClientResponse callback, Object arg0) {
this.callback = callback;
apiService.postLogIn(Login.username, Login.password).enqueue(new Callback<LoginData>() {
#Override
public void onResponse(Call<LoginData> call, Response<LoginData> response) {
if (response.isSuccessful()) {
LoginData loginData = response.body();
successLoginData = loginData;
successCallBack();
} else {
ApiErrorHandler.handleError(context, response, errorResponse);
}
}
#Override
public void onFailure(Call<LoginData> call, Throwable t) {
ApiErrorHandler.handleError(context, t, errorResponse);
}
RetrofitErrorResponse errorResponse = new RetrofitErrorResponse() {
#Override
public void errorMessage(String errorMessage) {
failureCallBack(errorMessage);
}
#Override
public void tSyncError() {
}
#Override
public void invalidTokenError() {
}
};
});
}
LoginData.java model class
#Generated("org.jsonschema2pojo")
public class LoginData {
#SerializedName("access_token")
#Expose
private String accessToken;
#SerializedName("token_type")
#Expose
private String tokenType;
#SerializedName("refresh_token")
#Expose
private String refreshToken;
#SerializedName("expires_in")
#Expose
private long expiresIn;
#SerializedName("scope")
#Expose
private String scope;
// getter setter
}
Here is my Spring boot application code example
MainApplicationClass.java
#SpringBootApplication
public class MainApplicationClass {
public static void main(String[] args) {
SpringApplication.run(MainApplicationClass.class, args);
}
}
Controller.java
#RestController
public class BlogController {
#Autowired
BlogRespository blogRespository;
BlogMockedData blogMockedData = BlogMockedData.getInstance();
#GetMapping("/blog")
public List<Blog> index() {
return blogMockedData.fetchBlogList();
}
#GetMapping("/blog/{id}")
public Blog show(#PathVariable String id) {
int blogId = Integer.parseInt(id);
return blogMockedData.getBlogById(blogId);
}
#PostMapping(value = "/api/login",
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_JSON_VALUE}
)
public LoginData postLogin(#RequestBody Map<String, String> body) {
String userName = body.get("username");
String password = body.get("password");
return blogMockedData.getLoginToken(userName, password);
}
NOTE: If I hit spring boot POST service from POSTMAN i get below result
But if I hit the POST service from my android client side it gets me the error
"status":415,"error":"Unsupported Media
Type","exception":"org.springframework.web.HttpMediaTypeNotSupportedException","message":"Content
type 'application/x-www-form-urlencoded;charset=UTF-8' not
supported","path":"/api/login"
For Spring to correctly load form encoded data you need to define the endpoint parameter as:
public LoginData postLogin(#RequestBody MultiValueMap<String, String> body)
or
public LoginData postLogin(#RequestParam Map<String, String> body)
When using application/x-www-form-urlencoded, Spring doesn't understand it as a RequestBody. So, remove the #RequestBody annotation in the parameter list of your mapped data as I can see it there.
Please read this Spring doesn't understand application/x-www-form-urlencoded
To process a URL Encoded POST in Spring Boot ( Content-Type: application/x-www-form-urlencoded )
This does not work:
public String processForm(#RequestBody RespFormDTO formDTO,
HttpServletRequest request, HttpServletResponse response) {
This will work:
public String processForm(RespFormDTO formDTO,
HttpServletRequest request, HttpServletResponse response) {
I ran into similar issue where when header Content-Type:application/x-www-form-urlencoded is added to my request to Springboot app. Request body is empty.
I followed instruction is below link and it worked.
Short answer , you need to disable HiddenHttpMethodFilter
Link below explains reason and how to disable
https://newbedev.com/why-is-httpservletrequest-inputstream-empty
add headers in your interface
public interface ApiService {
#FormUrlEncoded
#POST("/api/login")
#Headers({
"Content-Type: application/x-www-form-urlencoded",
"accept-encoding: gzip, deflate",
"Accept: application/json;charset=UTF-8"})
Call<LoginData> postLogIn(
#Field("username") String username,
#Field("password") String password);
}
I'm looking into optimal ways of accessing the HTTP request and response bodies for tracing in a Spring reactive application.
For previous versions, we've leveraged Servlet filters and Servlet request wrappers to consume the incoming request's input stream and hold a copy of it for asynchronous processing of the traces (we send them to Elasticsearch).
But for a Spring reactive app (using webflux), I'm wondering what'd be the most appropriate way to access the requests before they're decoded. Any thoughts?
Turns out this can be achieved using the provided decorators: ServerWebExchangeDecorator, ServerHttpRequestDecorator and ServerHttpResponseDecorator, respectively.
Here's a sample request decorator that accumulates the DataBuffer contents as its read by the request's default subscriber:
#Slf4j
public class CachingServerHttpRequestDecorator extends ServerHttpRequestDecorator {
#Getter
private final OffsetDateTime timestamp = OffsetDateTime.now();
private final StringBuilder cachedBody = new StringBuilder();
CachingServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
}
#Override
public Flux<DataBuffer> getBody() {
return super.getBody().doOnNext(this::cache);
}
#SneakyThrows
private void cache(DataBuffer buffer) {
cachedBody.append(UTF_8.decode(buffer.asByteBuffer())
.toString());
}
public String getCachedBody() {
return cachedBody.toString();
}
Just make sure that, when you decorate the ServerWebExchange passed by the WebFilter, you also override getRequest() to return the request decorator as well:
public final class PartnerServerWebExchangeDecorator extends ServerWebExchangeDecorator {
private final ServerHttpRequestDecorator requestDecorator;
private final ServerHttpResponseDecorator responseDecorator;
public PartnerServerWebExchangeDecorator(ServerWebExchange delegate) {
super(delegate);
this.requestDecorator = new PartnerServerHttpRequestDecorator(delegate.getRequest());
this.responseDecorator = new PartnerServerHttpResponseDecorator(delegate.getResponse());
}
#Override
public ServerHttpRequest getRequest() {
return requestDecorator;
}
#Override
public ServerHttpResponse getResponse() {
return responseDecorator;
}
}
On the filter:
#Component
public class TracingFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(new PartnerServerWebExchangeDecorator(exchange));
}
}
Which can be used as such (beware the statically imported functions):
#Bean
public HttpHandler myRoute(MyHandler handler) {
final RouterFunction<ServerResponse> routerFunction =
route(POST("/myResource"), handler::persistNotification);
return webHandler(toWebHandler(routerFunction))
.filter(new TracingFilter())
.build();
}
I have a play framework project that provides APIs that shared between multiple front-ends, currently, I'm working on single front-end but I want to create a multi-tenant backend, each front-end got its own Firebase account.
My problem that I have to consider which firebase project to access depends on the request header value, that came with different values depends on the front end.
What I have now:
FirebaseAppProvider.java:
public class FirebaseAppProvider implements Provider<FirebaseApp> {
private final Logger.ALogger logger;
private final Environment environment;
private final Configuration configuration;
#Inject
public FirebaseAppProvider(Environment environment, Configuration configuration) {
this.logger = Logger.of(this.getClass());
this.environment = environment;
this.configuration = configuration;
}
#Singleton
#Override
public FirebaseApp get() {
HashMap<String, String> firebaseProjects = (HashMap<String, String>) configuration.getObject("firebase");
firebaseProjects.forEach((websiteId, projectId) -> {
FileInputStream serviceAccount = null;
try {
serviceAccount = new FileInputStream(environment.classLoader().getResource(String.format("firebase/%s.json", projectId)).getPath());
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
FirebaseOptions options = new FirebaseOptions.Builder().setCredential(FirebaseCredentials.fromCertificate(serviceAccount))
.setDatabaseUrl(String.format("https://%s.firebaseio.com/", projectId))
.build();
FirebaseApp firebaseApp = FirebaseApp.initializeApp(options, projectId);
logger.info("FirebaseApp initialized");
});
return FirebaseApp.getInstance();
}
}
Also for Database:
FirebaseDatabaseProvider.java
public class FirebaseDatabaseProvider implements Provider<FirebaseDatabase> {
private final FirebaseApp firebaseApp;
public static List<TaxItem> TAXES = new ArrayList<>();
#Inject
public FirebaseDatabaseProvider(FirebaseApp firebaseApp) {
this.firebaseApp = firebaseApp;
fetchTaxes();
}
#Singleton
#Override
public FirebaseDatabase get() {
return FirebaseDatabase.getInstance(firebaseApp);
}
#Singleton
public DatabaseReference getUserDataReference() {
return this.get().getReference("/usersData");
}
#Singleton
public DatabaseReference getTaxesConfigurationReference() {
return this.get().getReference("/appData/taxConfiguration");
}
private void fetchTaxes() {
DatabaseReference bundlesRef = getTaxesConfigurationReference().child("taxes");
bundlesRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
TAXES.clear();
dataSnapshot.getChildren().forEach(tax -> TAXES.add(tax.getValue(TaxItem.class)));
Logger.info(String.format("==> %d taxes records loaded", TAXES.size()));
}
#Override
public void onCancelled(DatabaseError databaseError) {
Logger.warn("The read failed: " + databaseError.getCode());
}
});
}
}
So I bind them as well from Module.java:
public class Module extends AbstractModule {
#Override
public void configure() { bind(FirebaseApp.class).toProvider(FirebaseAppProvider.class).asEagerSingleton();
bind(FirebaseAuth.class).toProvider(FirebaseAuthProvider.class).asEagerSingleton();
bind(FirebaseDatabase.class).toProvider(FirebaseDatabaseProvider.class).asEagerSingleton();
}
}
my ActionCreator:
public class ActionCreator implements play.http.ActionCreator {
#Inject
public ActionCreator() {
}
#Override
public Action createAction(Http.Request request, Method actionMethod) {
switchTenancyId(request);
return new Action.Simple() {
#Override
public CompletionStage<Result> call(Http.Context ctx) {
return delegate.call(ctx);
}
};
}
private void switchTenancyId(Http.RequestHeader request) {
// DO something here
}
private Optional<String> getTenancyId(Http.RequestHeader request) {
String websiteId = request.getHeader("Website-ID");
System.out.println(websiteId);
return null;
}
}
What I want is when I use Database service, or auth service, I read the website id and decide which firebase project to access, I really tried the solution like this answer here:
Multi tenancy with Guice Custom Scopes and Jersey
Please note I'm willing to use differents projects, not the same firebase project for each front-end.
But kinda lost, especially the request can be only accessed from controller or ActionCreator, so what I got from the question above is load providers by key into ThreadLocal and switch them for each request depends on the annotation, but I was unable to do this because of the lack of knowledge.
The minimized version of my project can be found here: https://github.com/almothafar/play-with-multi-tenant-firebase
Also, I uploaded taxes-data-export.json file to import inside firebase project for a test.
Right, so I know Play a lot better than FireBase, but it seems to me you want to extract a tenancy ID from the request prior to feeding this into your FrieBase backend? Context when writing Java in play is Thread local, but even when doing things async you can make sure the Http.context info goes along for the ride by injecting the execution context. I would not do this via the action creator, unless you want to intercept which action is called. (Though I have a hackish solution for that as well.)
So, after a comment I'll try to elucidate here, your incoming request will be routed to a controller, like below (let me know if you need clearing up on routing etc):
Below is a solution for caching a retrieved FireBaseApp based on a "Website-ID" retrieved from the request, though I would likely put the tenancyId in the session.
import javax.inject.Inject;
import java.util.concurrent.CompletionStage;
public class MyController extends Controller {
private HttpExecutionContext ec; //This is the execution-context.
private FirebaseAppProvider appProvider;
private CacheApi cache;
#Inject
public MyController(HttpExecutionContext ec, FireBaseAppProvider provider,CacheApi cache) {
this.ec = ec;
this.appProvider = provider;
this.cache = cache;
}
/**
*Retrieves a website-id from request and attempts to retrieve
*FireBaseApp object from Cache.
*If not found a new FireBaseApp will be constructed by
*FireBaseAppProvider and cached.
**/
private FireBaseApp getFireBaseApp(){
String tenancyId = request.getHeader("Website-ID);
FireBaseApp app = (FireBaseApp)cache.get(tenancyId);
if(app==null){
app=appProvider.get();
cache.put(tenancyId,app);
}
return app;
}
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(() -> {
FireBaseApp app = getFireBaseApp();
//Do things with app.
}, ec.current()); //Used here.
}
}
Now in FireBaseAppProvider you can access the header via play.mvc.Controller, the only thing you need is to provide the HttpExecutionContext via ec.current. So (once again, I'm avoiding anything FireBase specific), in FireBaseProvider:
import play.mvc.Controller;
public class FireBaseAppProvider {
public String getWebsiteKey(){
String website = Controller.request().getHeader("Website-ID");
//If you need to handle a missing header etc, do it here.
return website;
}
public FireBaseApp get(){
String tenancyId = getWebsiteKey();
//Code to do actual construction here.
}
}
Let me know if this is close to what you're asking and I'll clean it up for you.
Also, if you want to store token validations etc, it's best to put them in the "session" of the return request, this is signed by Play Framework and allows storing data over requests. For larger data you can cache this using the session-id as part of the key.
I believe Custom Scopes for this is overkill. I would recommend doing the Request-Scoped seeding from Guice's own wiki. In your case that would be something like
public class TenancyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String tenancyId = httpRequest.getHeader("YOUR-TENANCY-ID-HEADER-NAME");
httpRequest.setAttribute(
Key.get(String.class, Names.named("tenancyId")).toString(),
userId
);
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException { }
#Override
public void destroy() { }
};
It has to be bound in a ServletModule
public class YourModule extends ServletModule {
#Override
protected void configureServlets() {
filter("/*").through(TenancyFilter.class);
}
#Provides
#RequestScoped
#Named("tenancyId")
String provideTenancyId() {
throw new IllegalStateException("user id must be manually seeded");
}
}
Then anywhere you need to get the Tenancy ID you just inject
public class SomeClass {
private final Provider<String> tenancyIdProvider;
#Inject
SomeClass(#Named("tenancyId") Provider<String> tenancyIdProvider) {
this.tenancyIdProvider = tenancyIdProvider;
}
// Methods in request call tenancyIdProvider.get() to get and take action based on Tenancy ID.
}
I am using a spring controller which returns a string from a threadsafe method.
so i have made the controller also thread safe.
I want to know how many request are there in queue simultaneously which are calling to the spring controller
Here is my suggestion how you can solve your issue.
Imagine you have this #Controller:
#Controller
public class MyController {
#Autowired
private IMyService service;
#RequestMapping(value = "/myPathName", method = RequestMethod.GET)
public String home(HttpServletRequest request, Model model) {
// you synchronized call
service.callSynchronized(request, model);
return "someJsp";
}
// +edit
#ResponseBody
#RequestMapping(value = "/queueStatus", method = RequestMethod.GET)
public String home(HttpServletRequest request) {
//
return "inQueue: " + request.getAttribute("inQueue");
}
}
Now you can define an interceptor and count the requests before and after execution:
public class RequestsInWorkInterceptor extends HandlerInterceptorAdapter {
private static final Logger _logger = LoggerFactory.getLogger(RequestsInWorkInterceptor.class);
private final AtomicLong counter = new AtomicLong();
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String methodURI = request.getRequestURI();
long current;
if("myPathName".equals(methodURI){
current = counter.incrementAndGet();
_logger.debug("current {} clients in a queue", current);
} else {
current = counter.get(); // just get, no increment
}
// +edit: put the count in the request so you can get it in you controller
request.setAttribute("inQueue", current);
return super.preHandle(request, response, handler);
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
String methodURI = request.getRequestURI();
if("myPathName".equals(methodURI){
counter.decrementAndGet();
}
super.postHandle(request, response, handler, modelAndView);
}
}
You can get information about the number of active requests using Jetty's StatisticsHandler and JMX.
If you're using Jetty as an embedded container (the recommended approach), you can use an EmbeddedServletContainerCustomizer to set this up:
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
((JettyEmbeddedServletContainerFactory) container)
.addServerCustomizers(new JettyServerCustomizer() {
#Override
public void customize(Server server) {
MBeanContainer mbContainer = new MBeanContainer(
ManagementFactory.getPlatformMBeanServer());
server.addEventListener(mbContainer);
server.addBean(mbContainer);
StatisticsHandler statisticsHandler = new StatisticsHandler();
statisticsHandler.setHandler(server.getHandler());
server.setHandler(statisticsHandler);
}
});
}
};
}
You'll need to add a dependency on org.eclipse.jetty:jetty-jmx to get access to MBeanContainer.
You can try with spring boot actuator. On endpoint /metrics you should have field "httpsessions.active"
public class RequesInterceptor extends HandlerInterceptorAdapter {
private static Object lock = new Object();
private static int count = 0
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
sinchronize(lock){
count++;
}
}
}
Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}