Please help me on the below item:
Where exactly custom valve used instead of a filter?
In our application, I have created tomcat 9 custom valve and trying to access user principal from the valve. but in the valve, it returns null. but we are able to access from the filter. we have used form-based authentication.
User Principal will be available in Custom valve or is it available on in fliter?
Code snippet is provided below:
public class ContextInitializerValve extends ValveBase {
public ContextInitializerValve() {
System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
}
#Override
public void invoke(Request request, Response response) throws IOException, ServletException {
System.out.println("======================custom valve==============================");
Principal principal = request.getUserPrincipal();
}
}
Thanks in advance
You can think of valves as the equivalent for filters, but server-side, not on the application side. E.g. you'd deploy a valve to the server, and typically can't hot-deploy new versions of it without restarting the server. You could redeploy a filter by redeploying your application that contains the filter.
Both Valves and Filters are order-sensitive: When you use Valves (tomcat uses them for server-side authentication) and you are sure that a user is authenticated, but you don't get the principal, your valve seems to be running before tomcat's authentication valve. You can either make sure to have your valve configured in the correct order, or move the implementation to a filter, because filters always run after all valves have been running.
In principle, they're very similar. Filters are defined by the servlet spec, while Valves (being part of the server implementation) are defined by Tomcat.
Related
tldr; What's the appropriate way to register either a custom AuthenticationSuccessHandler or configure the SAMLRelayStateSuccessHandler to set the redirect URL Spring Security's SAML extension after a successful authentication?
I am new to Spring and Spring Security, but I've had some success getting the SAML extension to work for a Single Sign-On application. However, I've run into a problem where Okta (the IDP) and my application will loop continuously after successful authentication instead of going to the original URL. My suspicion is that this is caused by the fact that I am using Vaadin as well as Spring and once the Vaadin servlet loads it is doing something funky with or ignoring the Spring filters entirely. This is suggested a possible problem and solution in a blog article on combining filter-based Spring Security with Vaadin where the proposed solution is to register an AuthenticationSuccessHandler.
However, this is not as simple with the Spring Security SAML Extension as it is with, say, a simple form login or OpenID. For example, with a form login registing a success handler is as easy as
http.formLogin().successHandler(handler);
No such option is available with the SAML Extension. However, in another post Vladimír Schäfer (see "Spring saml - how remember request parameter when initiate login on SP, and procesing them after IdP response) suggests changing the AuthenticationSuccessHandler to a SAMLRelayStateSuccessHandler. The documentation suggests that this would be the perfect way to do it. However, I would prefer not to subclass SAMLEntryPoint.
My current SecurityConfiguration is entirely Java-based and follows Matt Raible's article on using Spring and Okta together. The SecurityConfiguration is as follows:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable() // Disable Spring's CSRF protection and use Vaadin's.
.authorizeRequests() // Everything else: Do SAML SSO
// Allow everyone to attempt to login
.antMatchers("/root/saml*").permitAll()
// But make sure all other requests are authenticated
.anyRequest().authenticated()
.and()
// Create a completely new session
.sessionManagement().sessionFixation().newSession()
.and()
// Configure the saml properties
.apply(saml())
.serviceProvider()
.keyStore()
.storeFilePath("saml/keystore.jks")
.password(this.password)
.keyname(this.keyAlias)
.keyPassword(this.password)
.and()
.protocol("https")
// Do I need to adjust this localhost piece?
.hostname(String.format("%s:%s", "localhost", this.port))
.basePath("/")
.and()
// Load the metadata from the stored properties
.identityProvider().metadataFilePath(this.metadataUrl);
}
```
What's the best way to register the AuthenticationSuccessHandler or configure the SAMLRelayStateSuccessHandler? Any other thoughts would be greatly appreciated too!
I'm using:
#Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =
new SavedRequestAwareAuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
super.onAuthenticationSuccess(request, response, authentication);
}
};
successRedirectHandler.setDefaultTargetUrl("/");
return successRedirectHandler;
}
It seems to avoid needing a separate securityContext.xml file.
The solution to registering a custom handler is to configure the Spring SAML extension using the sample securityContext.xml file provided in the Sample program. Full details are available by carefully reading the Spring SAML Extension guide. If you are using Spring Boot, you need to tell it to import the securityContext.xml file by adding an #ImportResource annotation. In my case, I removed all the code from my security configuration class, but left the annotations and added the #ImportResource as follows:
/**
* This class is the main security configuration for working with the
* Single-Sign On SAML backend. All it does it import the security context file
* and configure other annotation-based options. All of the real configuration
* options are in WEB-INF/securityContext.xml.
*
* #author Jay Jay Billings
*
*/
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
#ImportResource("WEB-INF/securityContext.xml")
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {}
Once the securityContext.xml file is in place and reconfigure for your application, you can add a custom handler in the successRedirectHandler bean:
<!-- Handler deciding where to redirect user after successful login -->
<bean id="successRedirectHandler"
class="com.example.app.customSuccessHandler">
</bean>
That is the answer to my question, but it did not fix my redirect loop problem on its own. The solution to the redirect loop also required 1) fixing the entity base URL in the MetadataGenerator bean, and 2) fixing the security:intercept-url pattern value to account for my modified (non-root) base URL. For information on the latter, see "This webpage has a redirect loop in spring-security application" on this site.
I'm trying to enable role based access control on a rest end point that I've setup using undertow, jersey and CDI. I initialize the servlet deployment as follows:
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(Main.class.getClassLoader())
.setContextPath("/rest")
.setDeploymentName("sv.war")
.addListeners(listener(Listener.class))
.setLoginConfig(new LoginConfig("KEYCLOAK", "some-realm"))
.setAuthorizationManager(auth) // my dummy for testing
.addServlets(servlet("jerseyServlet", ServletContainer.class)
.setLoadOnStartup(1)
.addInitParam("javax.ws.rs.Application", SystemViewApplication.class.getName())
.addMapping("/api/*"));
I enabled kecloak authentication based on this example code.
So, my server is started as:
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
PathHandler path = Handlers.path(Handlers.resource(staticResources).setDirectoryListingEnabled(false).setWelcomeFiles("index.html"))
.addPrefixPath("/rest", manager.start());
Undertow server = Undertow.builder()
.addHttpListener(8087, "localhost")
.setHandler(sessionHandling(addSecurity(exchange -> {
final SecurityContext context = exchange.getSecurityContext();
if (!context.isAuthenticated()) {
exchange.endExchange();
return;
}
log.info("Authenticated: {} {} {}", context.getMechanismName(), context.getAuthenticatedAccount().getPrincipal().getName(), context.getAuthenticatedAccount().getRoles());
// propagate the request
path.handleRequest(exchange);
})))
.build();
server.start();
Where the two methods sessionHandling() and addSecurity() are lifted from the example I've linked above.
The authentication works, I am forced to log in, and the Authenticated: .. logging line is printed out with the correct details. But, once it hits the servlet handling, the security context (and account) is lost. I've traced this call and I can see that at some point along the path, it's replaced by brand new SecurityContext which has a null account.
Now my question - is there some authentication mechanism that I am missing that would propagate the state after the keycloak authentication or can I just fix the undertow code and in the SecurityContext, if the passed in context is already correctly authenticated, accept that state and move on? (the latter doesn't seem right, I'm guessing it's because the could be different authentication for the servlet deployment?) If so, is there any way to connect the servlet deployment to see the keycloak authentication has already happened?
Incase anyone comes looking here on how to authenticate servlets properly with keycloak and use role based authentication, this worked for me (note, this worked for me without the requirement of any xml files, purely with annotations.
First in the servlet application (wherever you extended ResourceConfig) register() the RolesAllowedDynamicFeature.class.
Also enable "use-resource-role-mappings": true in keycloak.json.
Next, instantiate the servlet deployment with an initial security wrapper:
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(Main.class.getClassLoader())
.setContextPath("/")
.setDeploymentName("sv.war")
.addListeners(listener(Listener.class))
.setIdentityManager(idm)
.setSessionManagerFactory(new InMemorySessionManagerFactory())
.setInitialSecurityWrapper(handler -> sessionHandling(addSecurity(handler)))
.setResourceManager(staticResources)
.addWelcomePage("index.html")
.addServlets(servlet("jerseyServlet", ServletContainer.class)
.setLoadOnStartup(1)
.addInitParam("javax.ws.rs.Application", SystemViewApplication.class.getName())
.addMapping("/api/*"));
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
Undertow server = Undertow.builder()
.addHttpListener(8087, "localhost")
.setHandler(Handlers.path(manager.start()))
.build();
server.start();
Where sessionHandling(addSecurity(handler)) is basically the code from the linked github repo.
Now authentication via keycloak will work, and also role based authentication will work, so for example, if you have a CDI injected rest end point, such as:
#RolesAllowed({"admin", "guest"})
#GET
#Path("/{id}")
public Response findById(#PathParam("id") #NotNull Integer id){
// some method
}
As long as the roles are configured in keycloak, it should work.
Recently I have upgrade my all project API's like Spring, Spring Security, Hibernate, Maven, Java. Before upgrade I was using Spring 3 and Spring Security 2.
Now I am using Spring 4 and Spring Security 4 in my project and I have also used cas authentication for login.
When user logged in my application, based on the initial requested URL I want to set target URL of user. Before upgrade it was working fine.
I was using this SPRING_SECURITY_SAVED_REQUEST_KEY to get initial request URL.
Now I am using -
savedRequest = new HttpSessionRequestCache().getRequest(request, response);
to get initial request but it always return null.
Is there any way to get initial request of user after login?
you can use SavedRequestAwareAuthenticationSuccessHandler
Here is how I used the SavedRequestAwareAuthenticationSuccessHandler.
In my case I had an authorization server at localhost:8081 and the secured UI at localhost:8083/app.
In the Authorization server I created the following class:
#Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
setDefaultTargetUrl("http://localhost:8083/app");
super.onAuthenticationSuccess(request, response, authentication);
}
}
In my case there were 3 scenarios of user logging in:
User navigates to http://localhost:8081/login
User navigates to http://localhost:8083/app
User navigates to any secured url within the app like http://localhost:8083/app/files
The class above covers every one of these scenarios by redirecting the user to the original url or if the user choose to login via http://localhost:8081/login, it redirects them to the default target url which is http://localhost:8083/app.
In short - I would like to add such service endpoints to my servlet that can only be called from localhost. The restriction should be coded in the servlet itself, i.e it should not depend on Tomcat/Apache to be configured in a certain way. At the same time, there are many other, existing endpoints that should be reachable externally.
Longer description - I am creating an HTTP API that 3rd parties can implement to integrate with my application. I am also supplying a default implementation, bundled together with my app, that customers with simple requirements can use, without having to implement anything.
The endpoints of my default implementation should be reachable only for my app, which happens to be the same servlet as the one supplying the implementation, i.e it runs on the same host. So for security reasons (the API is security related), I want my implementation to be usable only for my app, which in the first round means restricting access to localhost for a set of HTTP endpoints.
At the same time, I don't want to rely on customers setting up their container/proxy properly, but do the restriction in my servlet, so that there are no changes required for existing installations.
So far the only idea I had was to check the requestor's IP addess in a servlet filter - so I am wondering if there is a better, more sophisticated way.
I think you should add Web Filter to your application and check your url in doFilter method. Check request.getRemoteAddr() and endpoint link you can put in urlPattern.
Like this:
#WebFilter(urlPatterns = "/*")
public class RequestDefaultFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (isForbidden(request, response))
return;
else
chain.doFilter(request, response);
}
}
isForbidden implementation is up to you. In response you just send 403 error code for example.
You can check make same check in servlet and send in response 403 error.
I'm working on an web application and have a problem of session storage.
The application is deployed to a cluster, and we place an apache http server to handle load-balancing. So each request may be handled by different node in the cluster. We set out to use Redis(master/slave) as a shared session storage.
My question is how to plug it to Spring mvc, so we can use redis and don't need change our application code.
I find a solution on the internet which uses Filter and HttpServletRequestWrapper:
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
//get sessionId from cookie or generate one if not found
filterChain.doFilter(new CustomHttpServletRequestWrapper(sid, request),
servletResponse);
}
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
String sid = "";
public HttpServletRequestWrapper(String sid, HttpServletRequest arg0) {
super(arg0);
this.sid = sid;
}
public HttpSession getSession(boolean create) {
return new HttpSessionSidWrapper(this.sid, super.getSession(create));
}
public HttpSession getSession() {
return new HttpSessionSidWrapper(this.sid, super.getSession());
}
}
I wonder whether I can find an equivalent in spring mvc?
The first one came to my mind is HandlerInterceptor. But not like filter's api, HanlderIntercepor does not handle the chain, so there is no way to pass a custom request to the next interceptor.
Any idea is appreciate, thanks in advance.
UPDATE:
I found two strategy on this topic:
The first one is extending your web container(such as Tomcat) and There is some mature open source project focused on it already.
The second one is use filter + wrapper. This stratey is web container agnositic.
Personally I prefer the first one because it's transparent to the developers. Let's say we use Weblogic in test/production evironment and we use embedded jetty for development because it's faster and requires less resources. In this situation, developer doesn't have to setup a session storage for development. On the other hand, however, every developer need to setup a session storage of his/her own if we adopt the second strategy. The alternative solution is provide a shared session storage for development environment or some configurations to swtich the sesson storage strategy(built-in for development, shared for test/production). I think it's easier to perform dependency(configuration) injection via spring than that via raw servlet filter and hence raise this question.
Does anyone know is there an out-of-box implementation of the first strategy for Weblogic by the way?
I don't know about WebLogic session storage configuration options, but I can comment on the filter based strategy.
You can implement the filter to be aware of its environment. You can add configuration for the development, which will say "don't wrap request" and enable wrapping only in production (or test) environment.
You can implement your filter to be Spring application context aware (check WebApplicationContextUtils) or even being managed by Spring's application context (via DelegatingFilterProxy). Then you will be able to pull out configuration values or use Spring profiles to set up the filter manually.