I have gone thru the steps listed in the document -
https://developer.okta.com/blog/2017/03/16/spring-boot-saml#run-the-app-and-login-with-okta
Everything works fine and I see SAML response getting generated and reditection happening to Application from OKTA but when the request reaches the application, I get this error-
type=Forbidden, status=403). Invalid CSRF Token 'null' was found on
the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
I have tried disabling csrf but then it goes in infinite loop with SAML redirection.
Here's SecurityConfiguration.java-
package com.example;
import static org.springframework.security.extensions.saml2.config.SAMLConfigurer.saml;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${security.saml2.metadata-url}")
String metadataUrl;
#Value("${server.ssl.key-alias}")
String keyAlias;
#Value("${server.ssl.key-store-password}")
String password;
#Value("${server.port}")
String port;
#Value("${server.ssl.key-store}")
String keyStoreFilePath;
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/saml*").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml())
.serviceProvider()
.keyStore()
.storeFilePath("saml/keystore.jks")
.password(this.password)
.keyname(this.keyAlias)
.keyPassword(this.password)
.and()
.protocol("https")
.hostname(String.format("%s:%s", "10.200.10.10", this.port))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath(this.metadataUrl);
}
}
Any suggestion is appreciated.
The only difference I see in your code vs. my blog post is the following line:
.hostname(String.format("%s:%s", "10.200.10.10", this.port))
Do things work if you change it to the following?
.hostname(String.format("%s:%s", "localhost", this.port))
This issue is resolved.. I did couple of things-
In OKTA added destination url same as Single sign on URL- https://localhost:8443/saml/SSO
Also, on Spring side, I have disabled CSRF protection in SecurityConfiguration-
http.csrf().disable();
But really the issue was wrong destination url.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/saml").permitAll()
.anyRequest().authenticated().and().csrf().csrfTokenRepository(getCsrfTokenRepository());
}
private CsrfTokenRepository getCsrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookiePath("/");
return tokenRepository;
}
Add CSRF cookie. In the front end, if you are using Angular just import HttpClientXsrfModule. This would fetch the cookie value and set request header X-XSRF-TOKEN header
#Note : The configuration for saml login with still be the same. The above code shows, how to add csrf token.
Related
Is it possible to remove response headers with a RestFilter? Looking at this cookbook you would say it should be possible. However, the filter is only called when the request is incoming, before the call to the resource class. I was expecting to have a hook where I can modify the response headers before sending it back to the client.
i had a look at CORSFilter as an example, but it only sets headers, not remove them.
To be more specific, I want to remove the WWW-Authenticate header that is set by the Auth provider when the session has expired. This header causes a popup in the browser (chrome) that is undesirable.
what you need is a javax.ws.rs.container.ContainerRequestFilter. In jax-rs such filters can be registered in a javax.ws.rs.core.Application. The application used in ICM is com.intershop.component.rest.internal.application.DefaultRestApplication which can be adapted using an com.intershop.component.rest.internal.application.ApplicationClassesProvider that can be registered using a Set-Binding.
So you could create a Guice-Module and your filter:
public class MyRestModule extends AbstractModule
{
#Override
protected void configure()
{
Multibinder<ApplicationClassesProvider> binder = Multibinder.newSetBinder(binder(),
ApplicationClassesProvider.class);
binder.addBinding().toInstance(c->c.accept(MyResponseFilter.class));
}
}
public class MyResponseFilter extends ContainerRequestFilter
{
#Override
public void filter(ContainerRequestContext request, ContainerResponseContext response)
{
response.getHeaders().remove("WWW-Authenticate");
}
}
Please note that this filter will be applied to all requests, so please make sure you remove headers only for requests you really care about.
I implemented rest web services with Spring. When I deployed it in Eclipse as a Spring Boot Application, it works. However when I deployed it in Tomcat 7 on the same machine, it does not work. The error message is as follows:
XMLHttpRequest cannot load http://localhost:8080/ristoreService/oauth/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8081' is therefore not allowed access.
My CORS filter looks like this:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8081");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me, "
+ "Origin,Access-Control-Request-Method, Access-Control-Request-Headers, Authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
If I comment out response.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8081");, I still get the same error. It wouldn't work without this line even if I deploy in Eclipse. Why does it act differently being deployed under different environment on the same ip?
EDIT:
I tested the url http://localhost:8080/ristoreService/oauth/tokenwith rest client tester "CocoaRestClient" and got 404. So I made up a url which apparently does not exist http://localhost:8080/xxxxx and run it in UI (angularjs) and again got the CORS error. I think the error is kind of misleading, it is after all a 404. But why does it complain not found when the war was deployed successfully with the name ristoreService.war under webapps in Tomcat?
Try using a FilterRegistrationBean. Looks like this in Java Config:
#Bean
public FilterRegistrationBean authorizationFilter(){
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
filterRegBean.setFilter(authorizationFilter);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/v1/*");
filterRegBean.setUrlPatterns(urlPatterns);
return filterRegBean;
}
Any reason why you're not using Spring Boot's CORS capabilities? It's already supported out of the box, you just gotta configure it. You can enable it globally like this:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
According to How to deploy Spring Boot application, I have to make main application to extend SpringBootServletInitializer. Once I added that, it works.
To solve CORS issue I used #CrossOrigin. And I did not implement my own CORS filter. Any way spring already have provided few addition solutions for CORS issue.
If you need only your filter you could use it in this way:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(yourFilter);
...
}
I'm using a default JSF servlet and RestEasy servlet to serve URI requests (Wildfly 8.1). I want every single URI request to be logged with a #SessionScoped backing bean. Either CDI bean (#Named) or ManagedBean (#ManagedBean) so that I can log the http requests from this visitor.
My requirements:
I don't want to invoke the logging of the access from each JSF page,
nor from each REST Resource Java file.
Every request must be linkable to #SessionScoped annotated backing bean Visit. The Visit object stores:
a user (if identified)
start of visit
an IP Address
n URI requests in a list: JSF resource requests and rest resource requests
My questions:
How do I register a filter in web.xml that logs both requests - be it JSF or REST - to the #SessionScoped annotated backing bean Visit?
If I could access this backing bean, how do I ensure that it is the session fo the same user? This session management of the web container is unclear to me. How does the web container map the request to a known session instance? By a default cookie?
Of course there is already a servlet-mapping on the url-pattern /* and one on /restresources/* One could not register 2 filters for the same path, could you? :
<filter>
<filter-name>UriLogger</filter-name>
<filter-class>com.doe.filters.UriAccessLogger</filter-class>
</filter>
<filter-mapping>
<filter-name>UriLogger</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Okay. For others that want to log every page and REST resource access, too.
Create the filter in the web.xml file.
<filter>
<filter-name>UriLogger</filter-name>
<filter-class>com.doe.filters.UriLoggingFilter </filter-class>
</filter>
<filter-mapping>
<filter-name>UriLogger</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Also, create the filter class.
package com.doe.webapp.controller.general.filters;
import java.io.IOException;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import com.doe.webapp.controller.general.VisitController;
#Named
#SessionScoped
public class UriLoggingFilter implements Serializable, Filter {
private static final long serialVersionUID = 1472782644963167647L;
private static Logger LOGGER = Logger.getLogger(UriLoggingFilter.class);
private String lastLoggedUri = "";
FilterConfig filterConfig = null;
#Inject
VisitController visitController;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
/**
* Log requests of interest with the VisitController.
*/
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
ServletException {
// Run the other filters.
filterChain.doFilter(request, response);
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String uri = httpServletRequest.getRequestURI();
String regex = "((/{1}\\w+$)|(/{1}\\w+\\.jsf$))";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(uri);
while (m.find()) {
LOGGER.info("match " + m.group());
if (!lastLoggedUri.equals(uri)) {
visitController.saveUriRequest(httpServletRequest);
lastLoggedUri = uri;
} else {
LOGGER.warn("Multiple URI access to the same resource of the same user: " + uri);
}
break;
}
}
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
In this code I removed the logging of repetitive requests. Only jsf page requests and REST resource requests are logged. Thus, no images, css or js requests. Adapt the RegEx according to your own needs. The EJB function saveUriRequest I have annotated with #Asynchronous, to avoid laggy delays of the response.
Answering my own questions:
The filter will pick up every single http request - be it a JSF page or REST resource call. Annotate the Filter as a CDI bean with #Named and #SessionScoped. Now you have a filter for every single visitor. A WORD OF CAUTION - DON'T DO THIS IF YOU HAVE HIGH NUMBER OF DIFFERENT USERS. THIS WILL RAPIDLY BRING DOWN YOUR AVAILABLE MEMORY. Alternatively you could mark it as #ApplicationScoped and get a visitor id from the ServletRequest request header instance and assign the request to a visitor. Also, this is prone to Denials-Of-Service attacks. (I'm using this only for internal purpose.)
Yes, the web container distriguishes between sessions by a jsessionid also from the ServletRequest request.
Hope this helps someone, too.
There are lots of guidelines, sample codes that show how to secure REST API with Spring Security, but most of them assume a web client and talk about login page, redirection, using cookie, etc. May be even a simple filter that checks for the custom token in HTTP header might be enough. How do I implement security for below requirements? Is there any gist/github project doing the same? My knowledge in spring security is limited, so if there is a simpler way to implement this with spring security, please let me know.
REST API served by stateless backend over HTTPS
client could be web app, mobile app, any SPA style app, third-party APIs
no Basic Auth, no cookies, no UI (no JSP/HTML/static-resources), no redirections, no OAuth provider.
custom token set on HTTPS headers
The token validation done against external store (like MemCached/Redis/ or even any RDBMS)
All APIs need to be authenticated except for selected paths (like /login, /signup, /public, etc..)
I use Springboot, spring security, etc.. prefer a solution with Java config (no XML)
My sample app does exactly this - securing REST endpoints using Spring Security in a stateless scenario. Individual REST calls are authenticated using an HTTP header. Authentication information is stored on the server side in an in-memory cache and provides the same semantics as those offered by the HTTP session in a typical web application. The app uses the full Spring Security infrastructure with very minimum custom code. No bare filters, no code outside of the Spring Security infrastructure.
The basic idea is to implement the following four Spring Security components:
org.springframework.security.web.AuthenticationEntryPoint to trap REST calls requiring authentication but missing the required authentication token and thereby deny the requests.
org.springframework.security.core.Authentication to hold the authentication information required for the REST API.
org.springframework.security.authentication.AuthenticationProvider to perform the actual authentication (against a database, an LDAP server, a web service, etc.).
org.springframework.security.web.context.SecurityContextRepository to hold the authentication token in between HTTP requests. In the sample, the implementation saves the token in an EHCACHE instance.
The sample uses XML configuration but you can easily come up with the equivalent Java config.
You're right, it isn't easy and there aren't many good examples out there. Examples i saw made it so you couldn't use other spring security stuff side by side. I did something similar recently, here's what i did.
You need a custom token to hold your header value
public class CustomToken extends AbstractAuthenticationToken {
private final String value;
//Getters and Constructor. Make sure getAutheticated returns false at first.
//I made mine "immutable" via:
#Override
public void setAuthenticated(boolean isAuthenticated) {
//It doesn't make sense to let just anyone set this token to authenticated, so we block it
//Similar precautions are taken in other spring framework tokens, EG: UsernamePasswordAuthenticationToken
if (isAuthenticated) {
throw new IllegalArgumentException(MESSAGE_CANNOT_SET_AUTHENTICATED);
}
super.setAuthenticated(false);
}
}
You need a spring security filter to extract the header and ask the manager to authenticate it, something like thisemphasized text
public class CustomFilter extends AbstractAuthenticationProcessingFilter {
public CustomFilter(RequestMatcher requestMatcher) {
super(requestMatcher);
this.setAuthenticationSuccessHandler((request, response, authentication) -> {
/*
* On success the desired action is to chain through the remaining filters.
* Chaining is not possible through the success handlers, because the chain is not accessible in this method.
* As such, this success handler implementation does nothing, and chaining is accomplished by overriding the successfulAuthentication method as per:
* http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication)
* "Subclasses can override this method to continue the FilterChain after successful authentication."
*/
});
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String tokenValue = request.getHeader("SOMEHEADER");
if(StringUtils.isEmpty(tokenValue)) {
//Doing this check is kinda dumb because we check for it up above in doFilter
//..but this is a public method and we can't do much if we don't have the header
//also we can't do the check only here because we don't have the chain available
return null;
}
CustomToken token = new CustomToken(tokenValue);
token.setDetails(authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(token);
}
/*
* Overriding this method to maintain the chaining on authentication success.
* http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication)
* "Subclasses can override this method to continue the FilterChain after successful authentication."
*/
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//if this isn't called, then no auth is set in the security context holder
//and subsequent security filters can still execute.
//so in SOME cases you might want to conditionally call this
super.successfulAuthentication(request, response, chain, authResult);
//Continue the chain
chain.doFilter(request, response);
}
}
Register your custom filter in spring security chain
#Configuration
public static class ResourceEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {
//Note, we don't register this as a bean as we don't want it to be added to the main Filter chain, just the spring security filter chain
protected AbstractAuthenticationProcessingFilter createCustomFilter() throws Exception {
CustomFilter filter = new CustomFilter( new RegexRequestMatcher("^/.*", null));
filter.setAuthenticationManager(this.authenticationManagerBean());
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//fyi: This adds it to the spring security proxy filter chain
.addFilterBefore(createCustomFilter(), AnonymousAuthenticationFilter.class)
}
}
A custom auth provider to validate that token extracted with the filter.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
CustomToken token = (CustomToken)auth;
try{
//Authenticate token against redis or whatever you want
//This i found weird, you need a Principal in your Token...I use User
//I found this to be very redundant in spring security, but Controller param resolving will break if you don't do this...anoying
org.springframework.security.core.userdetails.User principal = new User(...);
//Our token resolved to a username so i went with this token...you could make your CustomToken take the principal. getCredentials returns "NO_PASSWORD"..it gets cleared out anyways. also the getAuthenticated for the thing you return should return true now
return new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), principal.getAuthorities());
} catch(Expection e){
//TODO throw appropriate AuthenticationException types
throw new BadCredentialsException(MESSAGE_AUTHENTICATION_FAILURE, e);
}
}
#Override
public boolean supports(Class<?> authentication) {
return CustomToken.class.isAssignableFrom(authentication);
}
}
Finally, register your provider as a bean so the authentication manager finds it in some #Configuration class. You probably could just #Component it too, i prefer this method
#Bean
public AuthenticationProvider createCustomAuthenticationProvider(injectedDependencies) {
return new CustomAuthenticationProvider(injectedDependencies);
}
The code secure all endpoints - but I'm sure that you can play with that :). The token is stored in Redis using Spring Boot Starter Security and you have to define our own UserDetailsService which you pass into AuthenticationManagerBuilder.
Long story short - copy paste EmbeddedRedisConfiguration and SecurityConfig and replace AuthenticationManagerBuilder to your logic.
HTTP:
Requesting token - sending basic HTTP auth content in a request header. A token is given back in a response header.
http --print=hH -a user:password localhost:8080/v1/users
GET /v1/users HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Basic dXNlcjpwYXNzd29yZA==
Connection: keep-alive
Host: localhost:8080
User-Agent: HTTPie/0.9.3
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
Date: Fri, 06 May 2016 09:44:23 GMT
Expires: 0
Pragma: no-cache
Server: Apache-Coyote/1.1
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af
Same request but using token:
http --print=hH localhost:8080/v1/users 'x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af'
GET /v1/users HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8080
User-Agent: HTTPie/0.9.3
x-auth-token: cacf4a97-75fe-464d-b499-fcfacb31c8af
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
Date: Fri, 06 May 2016 09:44:58 GMT
Expires: 0
Pragma: no-cache
Server: Apache-Coyote/1.1
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
If you pass wrong username/password or token you get 401.
JAVA
I added those dependencies into build.gradle
compile("org.springframework.session:spring-session-data-redis:1.0.1.RELEASE")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-web")
compile("com.github.kstyrc:embedded-redis:0.6")
Then Redis configration
#Configuration
#EnableRedisHttpSession
public class EmbeddedRedisConfiguration {
private static RedisServer redisServer;
#Bean
public JedisConnectionFactory connectionFactory() throws IOException {
redisServer = new RedisServer(Protocol.DEFAULT_PORT);
redisServer.start();
return new JedisConnectionFactory();
}
#PreDestroy
public void destroy() {
redisServer.stop();
}
}
Security config:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
Usually in tutorials you find AuthenticationManagerBuilder using inMemoryAuthentication but there is a lot more choices (LDAP, ...) Just take a look into class definition. I'm using userDetailsService which requires UserDetailsService object.
And finally my user service using CrudRepository.
#Service
public class UserService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserAccount userAccount = userRepository.findByEmail(username);
if (userAccount == null) {
return null;
}
return new User(username, userAccount.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
Another Example Project which uses JWT - Jhipster
Try Generating a Microservice application using JHipster. It generates a template with out of the box integration between Spring Security and JWT.
https://jhipster.github.io/security/
I recommend JSON Web Tokens http://jwt.io/ , it's stateless and scalable.
Here is an example project, https://github.com/brahalla/Cerberus
In my CQ5.6 application,. as soon as the user hits a URL, I need to edit it using a certain parameters. All this must happen before Sling starts processing the URL.
I basically need to convert the URL like:
www.mysite.fr --> converts to --> /content/mysite/fr/
and so on....
I understand I'll need to create an OSGi bundle for this, but which API should I use to ensure that the URL is filtered by my class first and then catered by
Sling. ?
if you want a code-based solution for multiple websites (and you don't want to manage /etc/map) you can setup your own Filter:
package your.package;
import org.apache.felix.scr.annotations.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.component.ComponentContext;
#Component(immediate=true, enabled=true)
#Service(value=Filter.class)
#Properties({
#Property(name="sling.filter.scope", value="REQUEST", propertyPrivate=true),
#Property(name="service.ranking", intValue=-10000, propertyPrivate=true)
})
public class YourFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(ProductSEOFilter.class);
#Activate
protected void activate(ComponentContext ctx) throws Exception {
}
#Deactivate
protected void deactivate() throws Exception {
}
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws java.io.IOException, ServletException {
String lang = "en";
// 1. get domain and path
// 2. check if your conditions are met
// 3. extract language from domain
// 4. internal redirect
RequestDispatcher dispatch = request.getRequestDispatcher("/content/mysite/" + lang);
dispatch.forward(request, response);
}
public void destroy() {
}
}
you don't need to bother checking for and passing querystrings--those are carried on in the dispatcher. it only needs a new url to forward to.
You can do this via Sling URL Mapping without the need for a filter. The simpliest way to achieve this is to create a node under the /etc/map directory with a resource type of sling:Mapping & called www.mysite.fr.
This then takes a property of sling:internalRedirect — if an incoming request matches the node name, this property is appended to the path to continue with internal resource resolution.
<map>
<http jcr:primaryType="sling:OrderedFolder">
<www.mysite.fr
jcr:primaryType="sling:Mapping"
sling:internalRedirect="/content/mysite/fr"/>
</http>
</map>
The above will ensure any request coming to www.mysite.fr is resolved to www.mysite.fr/content/mysite/fr.
You can also pattern matching based on regex properties rather than names & include port numbers or schemes too. The full documentation is available on the Sling website.