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.
Related
Currently in my SecurityConfig.java class file where I define my KeycloakWebSecurityConfigurerAdapter I want to define so that every GET request can be done by two different roles. But only one role can do the other types of HTTP requests (POST, PUT, PATCH etc). How can this be achieved in my code below:
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers(HttpMethod.GET).hasAnyRole("user", "admin")
.anyRequest().hasRole("admin");
}
What happens is that when trying to do POST request I get access denied 403. GET requests works fine. Any ideas?
You should disable csrf on your configure method :
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable().authorizeRequests().anyRequest().authenticated();
}
}
You should not use KeycloakWebSecurityConfigurerAdapter nor anything else from Keycloak libs for Spring, it is deprecated.
Instead, you can follow this tutorial which proposes two solutions based on:
spring-boot-starter-oauth2-resource-server which requires quite some Java conf
spring-addons-webmvc-jwt-resource-server which enables to configure most of security from properties (way simpler than preceding)
All tutorials linked here show how to map Keycloak roles to spring-security authorities (and will keep CSRF protection enabled, even for stateless resource-servers).
Below is my SecurityConfiguration class, I am using.
#SpringBootApplication
#RestController
public class WebMvcConfig {
#Configuration
protected static class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/**").authenticated()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
}
After startup, as soon as I hit my URL (http://localhost:8080/TestApp/), it takes me to the default login page and when I enter the default user Id (user) and password (printed on the console), it take me to my index.html page mapped by the "/" URL via my AngularJS routing. I am able to navigate through the UI but as soon as I submit any $http request (I am trying with a POST request), it gives me 403 on the browser console with my $http request URL.
Can someone help?
Error 403 means that you are forbidden from accessing all the resources.
If you inspect the error details, more probably you will have a message such as Expected CSRF token not found.
From v4 onwards, spring security enables by default csrf protection. This is a good practice as csrf attacks force an end user to execute unwanted actions on a web application in which they’re currently authenticated.
So in a dev environment, adding http.csrf().disable(); will solve your problem. But you should consider adding a csrf token when you want to move to a prod env.
I have 2 apps running, one is resource server where I have the info that needs authentication to view the text. Then I have authorization server that gives tokens. Right now I can use postman or Insomnia, add the auth_url, token_url, client_id, client_secret and I get the token. I add the token to header and i get do a get request to my resource server using header, and it works just fine.
Now i have no idea how to implement redirection from my resource server directly. Like when I go to
localhost:9000/home
I'd like to get redirected to:
localhost:9001/login
where I login with my inmemory user then it redirects me back to localhost:9000/home and I see the message.
What would be the best way to implement a way for user to access information on localhost:9000/home. You go to localhost:9000/home, it goes to authorization server on localhost:9001, you log in with username and password. Approve the grant, and it puts you back to localhost:9000/home and then you can see the text, what was previously protected, because you didn't have token to access it.
ResourceServer.java
#SpringBootApplication
#RestController
#EnableResourceServer
#EnableOAuth2Client
public class SampleResourceApplication extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").hasRole("user")
.anyRequest().authenticated();
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
public static void main(String[] args) {
SpringApplication.run(SampleResourceApplication.class, args);
}
#RequestMapping("/home")
public String home() {
return "this is home";
}
}
and my properties looks like:
server:
port: 900
security:
oauth2:
client:
client-id: foo
client-secret: foosecret
access-token-uri: http://localhost:9001/auth/oauth/token
user-authorization-uri: http://localhost:9001/auth/oauth/authorize
grant-type: USER
auto-approve-scopes: true
resource:
user-info-uri: http://localhost:9001/auth/user
Let's separate the agents: You have the user (i.e. you, also know as the resource owner), the authorization server, the resource server and the client (the application that access your urls, i.e. your browser).
Normally, this happens in your situation:
When your client access the resource server, it receives a 401. Depending of your implementation, you could also directly redirect the client to your AS (using a simple redirect response).
Your AS prompts you for credentials. After validating them, it issues a token for you. You can then use this token to access the RS.
What you're trying to get (if I understand correctly) is to redirect with the token automatically. To achieve this, you can simply pass the url you tried to reach (i.e. localhost:9000/home) when you redirect to your AS at the end of step 1. Your AS hten prompts the user for credentials, generate the token, stores it as a cookie (in the case of a browser), and redirects you to the url he received (localhost:9000/home).
EDIT: what's the resulting code for the redirection.
When you get to the configure, you first check if the user is authenticated. If he is, then all's fine, but if he isn't, you must catch this event and start your redirection. This can be done using the exceptionHandling method of the chaining http:
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").hasRole("user")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
// You can use a lambda here
#Override
public void commence(HttpServletRequest aRequest, HttpServletResponse aResponse,
AuthenticationException aAuthException) throws IOException, ServletException {
aResponse.sendRedirect(MY_AS_URL + "?redirect_uri=localhost:9001/home");
}
};
}
Unfortunately, I am not familiar with the Spring framework, but hopefully this helps anyways:
OAuth is an authorization protocol. It does not handle authentication (see also: "What is the difference between authentication and authorization?" on ServerFault).
If I understand you correctly, you want users to be redirected to /login when they go to /home and aren't already logged-in. This step has nothing to do with OAuth, it must be part of your application's security / firewall setup.
Please also note that there is a difference between logging in (authenticating) on the authorization server and actually granting the application the right to access your resources (authorization). First you have to prove who you are and only then can you give access to your stuff. These are two separate steps.
I've configured a custom authentication provider, a success handler and a failure handler in Spring Security (v4.0.1). When using the default ones, after displaying the login page, the user was redirected to the previously requested url. However, I lost that behaviour when implementing my own ones, so I'm trying to recover it.
Basically, right now, I'm being redirected to the home page everytime I log in, even I've accesed the login page trying to fetch another resource (in my case /web/users). That's the configuration I have right now:
#Configuration bean (extends WebSecurityConfigurerAdapter)
#Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/web/logout"))
.invalidateHttpSession(true).logoutSuccessUrl("/web/login");
http.authorizeRequests()
.antMatchers("/javax.faces.resource/**")
.permitAll()
.antMatchers("/web/recovery").permitAll()
.antMatchers("/web/users").authenticated().anyRequest()
.authenticated().and().formLogin()
.failureHandler(failureHandler).loginPage("/web/login")
.loginProcessingUrl("/web/j_spring_security_check")
.successHandler(successHandler).permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
DataSource ds, PasswordEncoder pwdEncoder) throws Exception {
auth.authenticationProvider(authProvider);
}
The custom AuthenticationSuccessHandler
public class SystemAuthenticationSuccessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
private IUserService service;
public SystemAuthenticationSuccessHandler(IUserService service) {
this.service = service;
setDefaultTargetUrl("/web/home");
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
String username = request.getParameter("username");
User user = service.findByIdOrEmail(username, username);
if (user != null) {
service.saveLoginSuccess(user.getId());
}
//Call the parent method to manage the successful authentication
super.onAuthenticationSuccess(request, response, authentication);
}
Basically, the problem I have is requestCache is always returning null for current request in the SavedRequestAwareAuthenticationSuccessHandler#onAuthenticationSuccess method and that's because the HttpSessionRequestCache#saveRequest method doesn't match my incoming request before being redirected to the login page.
Specifically, the HttpSessionRequestCache Spring is using is an AndRequestMatcher which discards all my incoming requests. I want it to use the AnyRequestMatcher, but don't know how to tell Spring that.
Update
Having set a breakpoint in HttpSessionRequestCache#setRequestMatcher, that's the concrete point where Spring Security sets it:
However, I don't know how to set a custom request cache configurer for my case! Isn't there an easier way for doing it?
Update 2
I've discovered right now this issue only happens when using Firefox and not with Chrome or Internet Explorer.
It's actually a problem with my current firefox browser. Even if I remove all the navigation and cache data, it keeps happening, but not with other browsers as Chrome or IE, not even with other FF browsers.
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.