I am securing my application using Spring Security 3.1.3 and I have a requirement to allow users to login via a link in a third-party application.
However, the link in the third-party application will redirect to a specific resource and not to the login page, where the resource that the user wishes
to access will be defined as a querystring parameter. So, for example, the link would be of the form :
//server.com/app/build/panel.jsp?resourceid='blah'
When a user clicks this link they should be taken to the login page defined in my Spring Security configuration and if authenticated then should be redirected
to the original link including the querystring parameter. The querystring parameter has no influence on how the user should be authenticated it's
merely an id of resource.
Now, this all works fine apart from the querystring, which gets stripped by Spring Security before it enters the request processing flow.
This is shown in the debug output from Spring Security;
org.springframework.security.web.savedrequest.HttpSessionRequestCache: DefaultSavedRequest added to Session:
DefaultSavedRequest[http://server.com:8080/app/build/panel.jsp]
ie, the querystring is not saved and resourceid='blah' has been removed.
Note, I'm currently using Ant matching. I have no need to actually match against the querystring.
In earlier versions of Spring Security, it seemed like you could influence this behaviour by using a BeanPostProcessor as per this post,
Spring Security - Url with request parameters rules ignored. But the method
DefaultFilterInvocationSecurityMetadataSource.setStripQueryStringFromUrls() has been removed from Spring Security 3.1.3.
How do I configure Spring Security to not strip the querystring from the original request? So that when the user is redirected after the login to
the original URL the querystring parameter will be retained?
Many Thanks
Howard
U can get it from SuccessHandler
SecurityConfiguration class
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
SuccessHandler getSuccessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/dashboard/**",
"/feedback/**"
).access("hasRole('ROLE_SYSTEM_ADMIN') or hasRole('ROLE_COMPANY_ADMIN')")
.and().formLogin().loginPage("/login").successHandler(getSuccessHandler)
.loginProcessingUrl("/login").usernameParameter("ssoId").passwordParameter("password")
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access_Denied")
.and()
.sessionManagement().invalidSessionUrl("/login").maximumSessions(1).expiredUrl("/login").and().sessionAuthenticationErrorUrl("/login").sessionFixation().migrateSession()
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS); //always, IF_REQUIRED,never ,stateless
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
.permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**")
.antMatchers("/images/**");
}
}
SuccessHandler class
#Component
public class SuccessHandler implements AuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpSession session = request.getSession();
response.sendRedirect(request.getContextPath() + "/dashboard/index");
}
}
Is basically the success handler.
You can take a look at this example:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/login*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.successHandler(new RefererAuthenticationSuccessHandler());
}
More info about it : http://www.baeldung.com/spring-security-redirect-login
For others on a similar issue, refer the link:
https://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html
Extract:There is a danger that when an application is deployed in a container which does not strip path parameters from these values, an attacker could add them to the requested URL in order to cause a pattern match to succeed or fail unexpectedly.
However this stripping is meant to firmly protect the pattern matching for login. It doesnt means the query parameters are not available from the HTTP request, they should be.
Related
Spring usually eagerly loading the spring security configuration while starting the application. I'm using OAuth with Spring Security
I'm maintaining a configuration table for storing the SSO related values (like jwk-url, client_id, client_secret). This values will be populated by an admin user via CRUD in the same spring boot application.
Then only the jwk-url is available to be configure in the Spring security configuration (refer below code - jwkSetUri(...)). This would not available at the application startup.
So I wanted to initialise the spring security configuration after the value is loaded into the table, like a lazy loading (#Lazy) at runtime. I know how to do Lazy loading of a regular class/service.
But still I'm not sure how to invoke the configure(HttpSecurity http) method at runtime and how to p
ass the HttpSecurity parameter. When I just try invoke new ResourceServerConfiguration() like a lazy loading at runtime, I don't see the configure() method is called. (Or) this class needs to be maintained as bean and lazy load whenever needed. But still not sure about how to call configure() in code.
Another thing is how to refresh/reload the spring security configuration at runtime, if the JWK url is changed by admin. Then only the spring security configuration can take effect of the changes.
#Configuration
#EnableWebSecurity
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.accessDeniedHandler(oAuth2AccessDeniedHandler)
.jwt()
// Some Auth server URL which would be fetch from table
.jwkSetUri(ssoConfigService.getActiveSSOCertificateURL());
// Eg. http://localhost:8090/auth/realms/demo-app/protocol/openid-connect/certs
}
}
I have already referred these links. But it doesn't help for my purpose. Any help would be appreciated.
How do I lazy load Spring Security?
How to reload the Configure method of WebSecurityConfigurerAdapter when the application is up and running
Modify Spring Security Config at Runtime
Configure Spring HTTP Security at Runtime
Please, check this link Customizing CORS Filtering at Runtime that include a similar use case related to your but for him, he needed to change allowed origins dynamically. They decide to create a new filter and simple extends OncePerRequestFilter.
Take in account to check the OAuth2ResourceServerProperties for your use case.
UPDATING:
Try with this code for this scenario:
Another thing is how to refresh/reload the spring security configuration at runtime, if the JWK url is changed by admin. Then only the spring security configuration can take effect of the changes.
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.anyRequest().authenticated()
// TODO: test with and without this and check if work for you
.and()
.oauth2ResourceServer()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.accessDeniedHandler(oAuth2AccessDeniedHandler)
.jwt()
// Some Auth server URL which would be fetch from table
.jwkSetUri(ssoConfigService.getActiveSSOCertificateURL());
// Eg. http://localhost:8090/auth/realms/demo-app/protocol/openid-connect/certs
http.addFilterBefore(new OncePerRequestFilter() {
// Every time a request occur, this method will be called.
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
try {
http.oauth2ResourceServer()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
.accessDeniedHandler(oAuth2AccessDeniedHandler)
.jwt()
// Some Auth server URL which would be fetch from table
.jwkSetUri(ssoConfigService.getActiveSSOCertificateURL());
} catch (Exception e) {
e.printStackTrace();
}
}
}, BasicAuthenticationFilter.class);
}
I hope this info can help you.
I have a fully working spring security process set up with some paths requiring authentication (via a token) and others I want to keep open and accessible without token. The issue I am running into is that when a request comes in to one of those open paths without the Authorization header, then the filters are ignored and the proper response is generated. However, when the Authorization header is present, even though on the ignored path, the request goes through the entire security filter chain when the ideal procedure would be to entirely skip the filter chain.
Below is my configuration.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(DEFAULT_IGNORE_REQUESTS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
authenticationTokenHeaderFilter.setAuthenticationManager(authenticationManager);
http.authorizeRequests()
.antMatchers("/example/**")
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
})
.authenticationEntryPoint(new HttpAuthenticationEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors()
.and()
.csrf().disable()
.addFilter(authenticationTokenHeaderFilter)
.addFilterBefore(new ExceptionTranslationFilter(
new Http403ForbiddenEntryPoint()),
authenticationTokenHeaderFilter.getClass()
);
}
public class AuthenticationTokenHeaderFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) {
return "N/A";
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
I have additionally tried putting the ignored paths to the HttpSecurity with permitAll() with no success.
Clarification
String[] DEFAULT_IGNORE_REQUESTS = new String[]{ "/actuator" };
In what is described above, any requests going to /example/** should go through the security chain and through my filter to make sure the user is authenticated. Any requests going to /actuator should not go through the security filter chain. The /example/** works correctly and as expected. The /actuator however does not.
When I make a request without the Authorization header, the security chain is not invoked.
When I make a request with the Authorization header present, the security chain is invoked and the Authorization value (token) is verified. In the event that the token is invalid, a custom exception gets thrown inside the filter. Even though the error gets thrown, I get the expected response from /actuator with a 200. The thrown error in this case however gets logged and a stack trace gets generated, which I do not want as it's not an error in that case.
In Spring Boot, any #Bean of type Filter gets added as a servlet filter. What's most likely happening is that your filter is being added as a filter entirely separate from the filter chain.
Instead of declaring your filter as a #Bean, you could initialize AuthenticationTokenHeaderFilter in your WebSecurityConfigurerAdapter and set the AuthenticationManager directly (which you already do anyways). So you can remove the #Autowired annotation in the filter.
I have been trying to configure Spring Boot security in order to allow some urls without requiring an authentication and not allowing any other requests without an authentication. I am having trouble achieving this.
As per my understanding, anyRequest().authenticated() requires previously declared antMatchers to require authentication.
How is it possible to achieve my requirement.
My Http Security configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers(HttpMethod.POST,SIGN_UP_URL).permitAll()
.antMatchers(HttpMethod.GET,banner_top_url).permitAll()
.antMatchers(HttpMethod.GET,banner_bottom_url).permitAll()
.antMatchers(HttpMethod.GET,javascript_url).permitAll()
.antMatchers(HttpMethod.GET,stylesheet_url).permitAll()
.antMatchers(HttpMethod.GET,photos_url).permitAll()
.antMatchers(HttpMethod.GET,transformed_photos_url).permitAll()
.antMatchers(HttpMethod.GET,preview_url).permitAll()
.antMatchers(HttpMethod.GET, "/", "/**/*.html", "/static/favicon.ico", "/**/*.js", "/**/*.js.map", "/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.jpeg", "/**/*.gif", "/**/*.ttf", "/**/*.json", "/**/*.woff", "/**/*.woff2", "/**/*.eot", "/**/*.svg").permitAll()// allows static content from resource folder
.antMatchers("/error").permitAll() // By default Security framework disables error pages (Unauthrorized)
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().addFilterBefore(jwtExceptionHandler,CorsFilter.class)
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().disable();
}
I assume that the below urls must be granted access without authentication.
SIGN_UP_URL
banner_top_url
banner_bottom_url
javascript_url
stylesheet_url
photos_url
transformed_photos_url
preview_url
The problem is this line : .anyRequest().authenticated()
If I remove it, then all the endpoints within the REST interface becomes available without authentication which I do not want.
Why aren't you excluding the static resource files globally via web.ignoring?
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
By default, Spring-security allows to pass everything. You have to tell Spring what can pass and what cannot pass. By removing anyRequest().authenticated you are telling to spring that everything that matches the patterns you mentioned are allowed to go and with the rest do what you do by default, that means, proceed. Here you are Spring Security doc: https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-httpsecurity
I have found that Spring-Working as intended. That being said,any antMAtchers will match the requestPath and not the resourcePath. An example is provided below.
*localhost:8080/image.jpg*
points at the root of the application which is src/main/resources/static/image.jpg
Now why is static used as a resource handler, that is because in the staticResourceConfiguration.java class I had the following lines
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
TL;DR
Is it possible to control the session creation policy in Spring (Security) on a per request basis?
Long version...
I have been using normal login form user authentication for our application.
Some of the controllers are #RestControllers and up to now, the default user session tracked by cookie has allowed it to work fine.
(I.e. when an XHR request comes from a page, the request is authenticated to the previously logged in user as the browser sends the JSESSIONID cookie as usual)
I now want to allow some of the #RestController end points to be called from a rest client, rather than browser, so I have created an API token authentication scheme - this works fine.
One of the last bits of cleanup is that the REST calls generate a session, which I'd like to avoid if possible.
I can't set the session policy to NEVER (because i'm still relying on sessions for my web users).
I have tried IF_REQUIRED to no avail.
I have looked at the HttpSessionSecurityContextRepository but it wraps the request, and a session is created whenever the response is flushed.
(See stacktrace below)
Is it possible elsewhere to hook into the session management on a per-request basis?
I can distinguish the type of request easily enough based on the class type of the Authentication object.
at myapp.cfg.WebConfig$1.sessionCreated(WebConfig.java:74)
at io.undertow.servlet.core.ApplicationListeners.sessionCreated(ApplicationListeners.java:300)
at io.undertow.servlet.core.SessionListenerBridge.sessionCreated(SessionListenerBridge.java:56)
at io.undertow.server.session.SessionListeners.sessionCreated(SessionListeners.java:52)
at io.undertow.server.session.InMemorySessionManager.createSession(InMemorySessionManager.java:187)
at io.undertow.servlet.spec.ServletContextImpl.getSession(ServletContextImpl.java:741)
at io.undertow.servlet.spec.HttpServletRequestImpl.getSession(HttpServletRequestImpl.java:370)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:270)
at org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper.createNewSessionIfAllowed(HttpSessionSecurityContextRepository.java:427)
at org.springframework.security.web.context.HttpSessionSecurityContextRepository$SaveToSessionResponseWrapper.saveContext(HttpSessionSecurityContextRepository.java:364)
at org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper.onResponseCommitted(SaveContextOnUpdateOrErrorResponseWrapper.java:85)
at org.springframework.security.web.util.OnCommittedResponseWrapper.doOnResponseCommitted(OnCommittedResponseWrapper.java:245)
at org.springframework.security.web.util.OnCommittedResponseWrapper.access$000(OnCommittedResponseWrapper.java:33)
at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.flush(OnCommittedResponseWrapper.java:512)
at org.springframework.security.web.util.OnCommittedResponseWrapper$SaveContextServletOutputStream.flush(OnCommittedResponseWrapper.java:513)
at com.fasterxml.jackson.core.json.UTF8JsonGenerator.flush(UTF8JsonGenerator.java:1050)
at com.fasterxml.jackson.databind.ObjectWriter.writeValue(ObjectWriter.java:953)
Split your security configuration into separate sections for a form login (session based API access) and a stateless API token authentication scheme.
Example:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Order(1)
#Configuration
class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic().realmName("API") // your API token authentication scheme
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.exceptionHandling().authenticationEntryPoint(new Http401AuthenticationEntryPoint("Form realm=\"API\"")); // prevent basic authentication popup in browser
}
}
#Order(2)
#Configuration
class DefaultSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin().loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login").permitAll();
}
}
}
Replace .httpBasic().realmName("API") with you own authentication scheme.
Call your API with e.g. curl -v ... and verify that there is no Set-Cookie header in the response. Otherwise your code somewhere creates an http session on its own.
You should try create-session policy as "stateless" for your API end points.
If "stateless" is used, this implies that the
application guarantees that it will not create a session. This differs from the use of
"never" which mans that Spring Security will not create a session, but will make use of
one if the application does.
I had the exact same problem and could not find a clean solution. In the absence of better options, I'll post a semi working hack.
DISCLAIMER: I have not used this solution (I fell back to sessions, at least for now), try it at your own risk.
Override the default SecurityContextRepository:
#Component
public class CustomSecurityContextRepository extends HttpSessionSecurityContextRepository {
#Override
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
SecurityContext securityContext = super.loadContext(requestResponseHolder);
// disable automatic saving of security context on response committed
// WARNING: not sure how safe this is
SaveContextOnUpdateOrErrorResponseWrapper response =
(SaveContextOnUpdateOrErrorResponseWrapper)requestResponseHolder.getResponse();
response.disableSaveOnResponseCommitted();
return securityContext;
}
#Override
public void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response) {
Authentication authentication = context.getAuthentication();
// call super.saveContext according to your use case
}
}
Finally, register this class in the WebSecurityConfigurerAdapter:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.securityContext().securityContextRepository(customSecurityContextRepository);
}
If anyone has a better solution I would be interested in hearing it.
I want to restrict certain rest endpoints to be only for LDAP users in a certain group.
I followed the guide https://spring.io/guides/gs/authenticating-ldap/ to setup LDAP authentication which is working perfectly. So how do I restrict certain rest endpoints?
I tried
#PreAuthorize("hasRole('developers')")
#RequestMapping("/foo")
public String foo(HttpServletRequest request) {
return "Welcome to FOO " + request.getRemoteUser();
}
but it still lets users not in the developers group access that endpoint
You can modify your WebSecurityConfigurerAdapter configuration to something like:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.antMatchers("/foo").hasRole("developers")
.and()
.formLogin();
}
I am not exactly sure of the syntax and if that first rule will override your second rule, but it will be similar to that.
Or, you can try configuring security on a method by method basis like this sample.
#EnableGlobalMethodSecurity(securedEnabled=true) needed to be added to the webSecurityConfig. Once I did that I was able to use #Secured("ROLE_DEVELOPERS") and that method was then restricted to that role.