Restrict API access by domain name [duplicate] - spring-boot

I have one war file for my application and I will be using 2 domains to access it. For example I want to access admin.jsp using admin.mydomain.com/adminpage and other jsp pages I want to access with local.mydomain.com.
Also, admin.jsp should be only accessible via admin.mydomain.com and not via local.mydomain.com. How to do this in spring-security / spring-mvc? Is there a support in spring framework for this?
Any help on this would be helpful. Thanks.

You can implement RequestMatcher, and maybe like
HostOnlyRequestMatch(String relativePath, String hostname)
and then override the boolean matches(HttpServletRequest request) method, and if the relativePath and hostname are same with request, return true.
Add the requestMatcher to http like this:
http
.authorizeRequests()
.requestMatcher(new HostOnlyRequestMatch("/admin", "admin.mydomain.com")).permitAll()
.antMatchers("/admin").denyAll();

One way would be to configure proxy (e.g. Nginx) to route your requests to your application server (e.g Tomcat) properly. Read here for more details https://www.nginx.com/resources/admin-guide/reverse-proxy/

You can get the requested url from request object in you mvc controller and if it is not form correct domain then you can throw or show proper error based on your project. Following is the code snippet
#Controller
#RequestMapping("/adminpage")
public class AdminPageController{
#RequestMapping(method = RequestMethod.GET)
public String getAdminPage(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if(!url.contains("admin.mydomain.com")) {
throw RuntimeException("Not accessible through this domain.");
// You can implement your own logic of showing error here
}
}
}

Related

How to redirect request to an URI passed in headers with Jetty AsyncProxyServlet

I'm creating a proxy micro-service with SpringBoot, Jetty and kotlin.
The purpose of this micro-service is to forward requests made by my front-end application to external services (avoiding CORS) and send back the response after checking some custom authentication. The query I'll receive will contain the URL of the target in the headers (i.e: Target-Url: http://domain.api/getmodel).
Based on this answer, I made a class that extends AsyncProxyServlet and overwrote the method sendProxyRequest :
class ProxyServlet : AsyncProxyServlet() {
private companion object {
const val TARGET_URL = "Target-Url"
}
override fun sendProxyRequest(clientRequest: HttpServletRequest, proxyResponse: HttpServletResponse, proxyRequest: Request) {
// authentication logic
val targetUrl = clientRequest.getHeader(TARGET_URL)
if (authSuccess) {
super.sendProxyRequest(clientRequest, proxyResponse, proxyRequest)
} else {
proxyResponse.status = HttpStatus.UNAUTHORIZED.value()
}
}
}
When I query my proxy, I get in this method and successfuly authenticate, but I fail to understand how to use my targetUrl to redirect the request.
The method keeps calling itself as it's redirecting the original request to itself (the request from http://myproxy:port/ to http://myproxy:port/).
It is very difficult to find documentation on this specific implementation of jetty, StackOverflow is my last resort!
First, setup logging for Jetty, and configure DEBUG level logging for the package namespace org.eclipse.jetty.proxy, this will help you understand the behavior much better.
The Request proxyRequest parameter represents a HttpClient/Request object, which is created with an immutable URI/URL destination (this is due to various other features that requires information from the URI/URL such as Connection pooling, Cookies, Authentication, etc), you cannot change the URI/URL on this object after the fact, you must create the HttpClient/Request object with the correct URI/URL.
Since all you want to do is change the target URL, you should instead be overriding the method ...
protected String rewriteTarget(HttpServletRequest clientRequest)
... and returning the new absolute URI String to the destination that you want to use (The "Target-Url" header in your scenario looks like a good candidate)
You can see this logic in the ProxyServlet.service(HttpServletRequest, HttpServletResponse) code block (which AsyncProxyServlet extends from)

How to filter request based on domain in spring-mvc

I have one war file for my application and I will be using 2 domains to access it. For example I want to access admin.jsp using admin.mydomain.com/adminpage and other jsp pages I want to access with local.mydomain.com.
Also, admin.jsp should be only accessible via admin.mydomain.com and not via local.mydomain.com. How to do this in spring-security / spring-mvc? Is there a support in spring framework for this?
Any help on this would be helpful. Thanks.
You can implement RequestMatcher, and maybe like
HostOnlyRequestMatch(String relativePath, String hostname)
and then override the boolean matches(HttpServletRequest request) method, and if the relativePath and hostname are same with request, return true.
Add the requestMatcher to http like this:
http
.authorizeRequests()
.requestMatcher(new HostOnlyRequestMatch("/admin", "admin.mydomain.com")).permitAll()
.antMatchers("/admin").denyAll();
One way would be to configure proxy (e.g. Nginx) to route your requests to your application server (e.g Tomcat) properly. Read here for more details https://www.nginx.com/resources/admin-guide/reverse-proxy/
You can get the requested url from request object in you mvc controller and if it is not form correct domain then you can throw or show proper error based on your project. Following is the code snippet
#Controller
#RequestMapping("/adminpage")
public class AdminPageController{
#RequestMapping(method = RequestMethod.GET)
public String getAdminPage(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if(!url.contains("admin.mydomain.com")) {
throw RuntimeException("Not accessible through this domain.");
// You can implement your own logic of showing error here
}
}
}

Spring-ws SoapHeader fields access in endpoint method

We are developing a contract-first WebService using spring-ws 2.2.0. We are trying to manage the authentication using a custom tag, named AuthToken, located in the SoapHeader.
The AuthToken has the following structure:
<authToken>
<username>USERNAME</xa:username>
<password>PASSWORD</xa:password>
</authToken>
We are able to generate a WSDL schema containing the specified custom authentication tag inside the SoapHeader.
The problem is that when the client performs the call towards our server we are not able to unmarshal the AuthToken tag (located in the SoapHeader) in our Ws Endpoint implementation.
Using the #RequestPayload annotation in the binding method signature (handleMethodRequest as specified in the example below), we are able to access the unmarshalled payload content (located in the SoapBody).
We tried to make the same thing with the SoapHeader content without success.
In the following code examples we show you what we would like to obtain:
1
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, #SoapHeader(value = "authToken") AuthToken authToken) { }
2
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, AuthToken authToken) { }
3
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, org.springframework.ws.soap.SoapHeader header) { }
4
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "methodRequest")
#ResponsePayload
public MethodResponse handleMethodRequest(#RequestPayload MethodRequest request, MessageContext messageContext) { }
In case 1, 2 we obtain the following error:
No adapter for endpoint [MethodResponse EndpointImplementation.handleMethodRequest(MethodRequest, AuthToken) throws java.lang.Exception]: Is your endpoint annotated with #Endpoint, or does it implement a supported interface like MessageHandler or PayloadEndpoint?
In case 3, 4 we have no errors but we are not able to handle SoapHeader or MessageContext (respectively in case 3 and 4) to reach our purposes, accessing the AuthToken to retrieve username and password sub element.
Looking for a solution in the web we found that many people having the same problem uses Spring Interceptors to handle the authentication.
Following the "Interceptors-way" we should access the AuthToken inside the interceptor. Unfortunately we need to use AuthToken field inside handleMethodRequest method for other purposes, for example loading user specific data, not accessible outside handleMethodRequest.
Therefore we cannot follow this way because we need to refer user specific data inside the handleMethodRequest method.
Does anyone know how can we solve the problem? Thanks in advance.
For that use case, the only supported combination of annotation and parameter type is #SoapHeader and SoapHeaderElement. Spring-WS currently doesn't support unmarshalling headers.
A hacky way of getting the value from interceptor to the handleMethodRequest is using a static ThreadLocal instance. Since the same thread that invokes the interceptor also invokes the handleMethodRequest you can use
ThreadLocal.set(AuthToken); // in interceptor.
ThreadLocal.get();// in handler and then clear it after use.
Also, I noticed that #SoapHeader(value = "{authToken") in your example does not have } is that a typo here or in your code?

How to control the web context of Spring Hateoas generated links?

We are building an API and are using Spring RestControllers and Spring HATEOAS.
When the war file is deployed to a container and a GET request is made to http://localhost:8080/placesapi-packaged-war-1.0.0-SNAPSHOT/places, the HATEOAS links look like this:
{
"links" : [ {
"rel" : "self",
"href" : "http://localhost:8080/placesapi-packaged-war-1.0.0-SNAPSHOT/places",
"lastModified" : "292269055-12-02T16:47:04Z"
} ]
}
in that the web context is that of the deployed application (eg: placesapi-packaged-war-1.0.0-SNAPSHOT)
In a real runtime environment (UAT and beyond), the container is likely to be sat behind a http server such as Apache where a virtual host or similar fronts the web application. Something like this:
<VirtualHost Nathans-MacBook-Pro.local>
ServerName Nathans-MacBook-Pro.local
<Proxy *>
AddDefaultCharset Off
Order deny,allow
Allow from all
</Proxy>
ProxyPass / ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
ProxyPassReverse / ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
</VirtualHost>
Using the above, when we make a GET request to http://nathans-macbook-pro.local/places, the resultant response looks like this:
{
"links": [ {
"rel": "self",
"href": "http://nathans-macbook-pro.local/placesapi-packaged-war-1.0.0-SNAPSHOT/places",
"lastModified": "292269055-12-02T16:47:04Z"
} ]
}
It's wrong because the link in the response contains the web app context, and if a client were to follow that link they would get a 404
Does anyone know how to control the behaviour of Spring HATEOAS in this respect? Basically I need to be able to control the web context name that it generates within links.
I did a bit of poking around and can see that with a custom header X-Forwarded-Host you can control the host and port, but I couldn't see anything similar to be able to control the context.
Other options we've considered involve either deploying the app to the ROOT context or to a fixed named context, and then set up our virtual host accordingly. However, these feel like compromises rather than solutions because ideally we would like to host several versions of the application on the same container (eg: placesapi-packaged-war-1.0.0-RELEASE, placesapi-packaged-war-1.0.1-RELEASE, placesapi-packaged-war-2.0.0-RELEASE etc) and have the virtual host forward to the correct app based on http request header.
Any thoughts on this would be very much appreciated,
Cheers
Nathan
First, in case you weren't aware, you can control the context of the web application (under Tomcat at least) by creating webapp/META-INF/context.xml containing the line:
<Context path="/" />
... which will make set the application context to be the same as what you are using (/).
However, that wasn't your question. I posed a similar question a little while back. As a result, from what I can gather, there's no out-of-the-box mechanism for controlling the generated links manually. Instead I created my own modified version of ControllerLinkBuilder, which built up the base of the URL using properties defined in application.properties. If setting the context on your application itself is not an option (i.e. if you're running multiple versions under the same Tomcat instance) then I think that this is your only option, if ControllerLinkBuilder is not building up your URLs correctly.
Had a very similar problem. We wanted our public URL to be x.com/store and internally our context path for hosts in a cluster was host/our-api. All the URLS being generated contained x.com/our-api and not x.com/store and were unresolvable from the public dirty internet.
First just a note, the reason we got x.com was because our reverse-proxy does NOT rewrite the HOST header. If it did we'd need to add an X-Forwarded-Host header set to x.com so HATEOAS link builder would generate the correct host. This was specific to our reverse-proxy.
As far as getting the paths to work...we did NOT want to use a custom ControllerLinkBuilder. Instead we rewrite the context in a servlet filter. Before i share that code, i want to bring up the trickiest thing. We wanted our api to generate useable links when going directly to the tomcat nodes hosting the war, thus urls should be host/our-api instead of host/store. In order to do this the reverse-proxy needs to give a hint to the web app that the request came through the reverse-proxy. You can do this with headers, etc. Specifically for us, we could ONLY modify the request url, so we changed our load balancer to rewrite x.com/store to host/our-api/store this extra /store let us know that the request came through the reverse-proxy, and thus needed to be using the public context root. Again you can use another identifier (custom header, presence of X-Forwared-Host, etc) to detect the situation..or you may not care about having individual nodes give back usable URLs (but it's really nice for testing).
public class ContextRewriteFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
//There's no cleanup to perform so no need for try/finally
chain.doFilter(new ContextRewriterHttpServletRequestWrapper(request), res);
}
private static class ContextRewriterHttpServletRequestWrapper extends HttpServletRequestWrapper {
//I'm not totally certain storing/caching these once is ok..but i can't think of a situation
//where the data would be changed in the wrapped request
private final String context;
private final String requestURI;
private final String servletPath;
public ContextRewriterHttpServletRequestWrapper(HttpServletRequest request){
super(request);
String originalRequestURI = super.getRequestURI();
//If this came from the load balancer which we know BECAUSE of the our-api/store root, rewrite it to just be from /store which is the public facing context root
if(originalRequestURI.startsWith("/our-api/store")){
requestURI = "/store" + originalRequestURI.substring(25);
}
else {
//otherwise it's just a standard request
requestURI = originalRequestURI;
}
int endOfContext = requestURI.indexOf("/", 1);
//If there's no / after the first one..then the request didn't contain it (ie /store vs /store/)
//in such a case the context is the request is the context so just use that
context = endOfContext == -1 ? requestURI : requestURI.substring(0, endOfContext);
String sPath = super.getServletPath();
//If the servlet path starts with /store then this request came from the load balancer
//so we need to pull out the /store as that's the context root...not part of the servlet path
if(sPath.startsWith("/store")) {
sPath = sPath.substring(6);
}
//I think this complies with the spec
servletPath = StringUtils.isEmpty(sPath) ? "/" : sPath;
}
#Override
public String getContextPath(){
return context;
}
#Override
public String getRequestURI(){
return requestURI;
}
#Override
public String getServletPath(){
return servletPath;
}
}
}
It's a hack, and if anything depends on knowing the REAL context path in the request it will probably error out...but it's been working nicely for us.
ProxyPass /placesapi-packaged-war-1.0.0-SNAPSHOT
ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
ProxyPassReverse /placesapi-packaged-war-1.0.0-SNAPSHOT
ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/

How to configure grails/spring authentication scheme per url?

How can I configure a grails application using Spring security such that one set of url's will redirect unauthenticated users to a custom login form with an http response code of 200, whereas another set of url's are implementing restful web services and must return a 401/not authorized response for unauthenticated clients so the client application can resend the request with a username and password in response to the 401.
My current configuration can handle the first case with the custom login form. However, I need to configure the other type of authentication for the restful interface url's while preserving the current behavior for the human interface.
Thanks!
If I understood right what you want to do, I got the same problem, before! but it is easy to solve it using Spring Security grails Plugin! So, first of all, you have to set your application to use basic authentication:
grails.plugins.springsecurity.useBasicAuth = true
So your restful services will try to login, and if it doesnt work it goes to 401!
This is easy but you also need to use a custom form to login right?! So you can just config some URL to gets into your normal login strategy like this:
grails.plugins.springsecurity.filterChain.chainMap = [
'/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter',
'/**': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]
So noticed, that above, everything that comes to the URL /api/ will use the Basic Auth, but anything that is not from /api/ uses the normal authentication login form!
EDIT
More information goes to http://burtbeckwith.github.com/grails-spring-security-core/docs/manual/guide/16%20Filters.html
I had the same issue and did not found a good solution for this. I am really looking forward a clean solution (something in the context like multi-tenant).
I ended up manually verifying the status and login-part for the second system, which should not redirect to the login page (so I am not using the "Secured" annotation). I did this using springSecurityService.reauthenticate() (for manually logging in), springSecurityService.isLoggedIn() and manually in each controller for the second system. If he wasn't, I have been redirecting to the specific page.
I do not know, whether this work-around is affordable for your second system.
You should make stateless basic authentication. For that please make following changes in your code.
UrlMappings.groovy
"/api/restLogin"(controller: 'api', action: 'restLogin', parseRequest: true)
Config.groovy
grails.plugin.springsecurity.useBasicAuth = true
grails.plugin.springsecurity.basic.realmName = "Login to My Site"
grails.plugin.springsecurity.filterChain.chainMap = [
'*' : 'statelessSecurityContextPersistenceFilter,logoutFilter,authenticationProcessingFilter,customBasicAuthenticationFilter,securityContextHolderAwareRequestFilter,rememberMeAuthenticationFilter,anonymousAuthenticationFilter,basicExceptionTranslationFilter,filterInvocationInterceptor',
'/api/': 'JOINED_FILTERS,-basicAuthenticationFilter,-basicExceptionTranslationFilter'
]
resources.groovy
statelessSecurityContextRepository(NullSecurityContextRepository) {}
statelessSecurityContextPersistenceFilter(SecurityContextPersistenceFilter, ref('statelessSecurityContextRepository')) {
}
customBasicAuthenticationEntryPoint(CustomBasicAuthenticationEntryPoint) {
realmName = SpringSecurityUtils.securityConfig.basic.realmName
}
customBasicAuthenticationFilter(BasicAuthenticationFilter, ref('authenticationManager'), ref('customBasicAuthenticationEntryPoint')) {
authenticationDetailsSource = ref('authenticationDetailsSource')
rememberMeServices = ref('rememberMeServices')
credentialsCharset = SpringSecurityUtils.securityConfig.basic.credentialsCharset // 'UTF-8'
}
basicAccessDeniedHandler(AccessDeniedHandlerImpl)
basicRequestCache(NullRequestCache)
basicExceptionTranslationFilter(ExceptionTranslationFilter, ref('customBasicAuthenticationEntryPoint'), ref('basicRequestCache')) {
accessDeniedHandler = ref('basicAccessDeniedHandler')
authenticationTrustResolver = ref('authenticationTrustResolver')
throwableAnalyzer = ref('throwableAnalyzer')
}
CustomBasicAuthenticationEntryPoint.groovy
public class CustomBasicAuthenticationEntryPoint extends
BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
ApiController
#Secured('permitAll')
class ApiController {
def springSecurityService
#Secured("ROLE_USER")
def restLogin() {
User currentUser = springSecurityService.currentUser
println(currentUser.username)
}
}

Resources