In my spring security config I've got the following settings:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/login.htm", "/signup.htm").permitAll()
.antMatchers("/page1.htm", "/page2.htm", "/page3.htm").access("#permission.hasPermission(principal.username))
....
}
The #permission which contains the method hasPermission is a #Component bean which decides whether the principal username has an access to the pages. In the bean I use my dao methods to determine this. However, I need more knowledge to make the decision because it's not a single page. For instance, is there any way to know what page the user has requested and pass that in the hasPermission method? In other words, I want to do something like:
.antMatchers("/page1.htm", "/page2.htm", "/page3.htm").access("#permission.hasPermission(principal.username, HttpServletRequest http))
See the 2nd parameter of the method. It's the http request which is the requested page so I will know whether the user requested page1, page2 or page3..
Or if I cannot pass that as a parameter how can I get the current requested page in my implementation of the hasPermission method?
You should be able to access it using the following:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/login.htm", "/signup.htm").permitAll()
.antMatchers("/page1.htm", "/page2.htm", "/page3.htm").access("#permission.hasPermission(principal.username,request))
....
}
This is due to the fact that the WebSecurityExpressionRoot.request property is exposed as a public final variable
Related
In the below code what do the different chained methods do?
protected void configure(HttpSecurity http ) throws Exception {
http.authorizeRequests()
.antMatchers(PUBLIC_URL).permitAll()
.anyRequest().authenticated();
}
NOTE: PUBLIC_URL is an array of strings containing public URLs.
authorizeRequests() Allows restricting access based upon the HttpServletRequest using RequestMatcher implementations.
permitAll() This will allow the public access that is anyone can access endpoint PUBLIC_URL without authentication.
anyRequest().authenticated() will restrict the access for any other endpoint other than PUBLIC_URL, and the user must be authenticated.
We can also configure access based on authorities, can manage the sessions, HTTPS channel and much more. You may find more details from configure(HttpSecurity http).
It means that all requests must be authenticated except those matching PUBLIC_URL
I just need to understand something in Spring Security Configuration. Using the example below...
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests().antMatchers("/secret/**").authenticated()
.and()
.authorizeRequests().antMatchers("/**").permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
}
What is the purpose of configure(WebSecurity web) method?
Can't I just add /resources/** in the configure(HttpSecurity http) method in this line .authorizeRequests().antMatchers("/**", "/resources/**").permitAll();
Shouldn't it work the same i.e. permitting all requests to /resources/** without any authentication?
General use of WebSecurity ignoring() method omits Spring Security and none of Spring Security’s features will be available.
WebSecurity is based above HttpSecurity.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**")
.antMatchers("/publics/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/publics/**").hasRole("USER") // no effect
.anyRequest().authenticated();
}
WebSecurity in the above example lets Spring ignore /resources/** and /publics/**. Therefore the .antMatchers("/publics/**").hasRole("USER") in HttpSecurity is unconsidered.
This will omit the request pattern from the security filter chain entirely.
Note that anything matching this path will then have no authentication or authorization services applied and will be freely accessible.
configure(HttpSecurity) allows configuration of web-based security at a resource level, based on a selection match - e.g. The example below restricts the URLs that start with /admin/ to users that have ADMIN role, and declares that any other URLs need to be successfully authenticated.
configure(WebSecurity) is used for configuration settings that impact global security (ignore resources, set debug mode, reject requests by implementing a custom firewall definition). For example, the following method would cause any request that starts with /resources/ to be ignored for authentication purposes.
Let's consider the below code, we can ignore the authentication for the endpoint provided within antMatchers using both the methods.
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/login", "/register", "/api/public/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/register", "/api/public/**").permitAll()
.anyRequest().authenticated();
}
configure(WebSecurity web)
Endpoint used in this method ignores the spring security filters, security features (secure headers, csrf protection etc) are also ignored and no security context will be set and can not protect endpoints for Cross-Site Scripting, XSS attacks, content-sniffing.
configure(HttpSecurity http)
Endpoint used in this method ignores the authentication for endpoints used in antMatchers and other security features will be in effect such as secure headers, CSRF protection, etc.
When you use HttpSecurity and try to permitAll() requests. Your requests will be allowed to be accessed from the Spring Security Filter Chain. This is costly as there will be requests other requests which would also come into this filter chain which needs to be allowed or disallowed based on Authentication/Authorization.
HttpSecurity.authorizeRequests().antMatchers("/**", "/resources/**").permitAll();
But when you use, any requests to resources will completely by pass the Spring Security Filter Chain all together. It is safe because you don't need any Authentication/Authorization to be in place to see an image or read a javascript file.
WebSecurity.ignoring().antMatchers("/resources/**");
I want to contribute to the great answers by including some code. There are three beans that are super important in Spring Security. based on their types they are DelegatingFilterProxy, FilterChainProxy and SecurityFilterChain.
DelegatingFilterProxy delegates the job of filtering requests to a bean of type FilterChainProxy which its name is springSecurityFilterChain, and FilterChainProxy is configured like this:
#Bean(
name = {"springSecurityFilterChain"}
)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {
});
this.webSecurity.apply(adapter);
}
return (Filter)this.webSecurity.build();
}
springSecurityFilterChain (or FilterChainProxy) itself has a list of SecurityFilterChain. SecurityFilterChain itself has a list of Filter instances that do the actual logic.
Every time we extend WebSecurityConfigurerAdapter and override the configure(HttpSecurity httpSecurity) method, we actually created a SecurityFilterChain that is going to be used by springSecurityFilterChain
How springSecurityFilterChain selects the appropriate SecurityFilterChain from the list? based on the boolean matches(HttpServletRequest request) method that is defined in the SecurityFilterChain interface.
So HttpSecurity is used to create a customized SecurityFilterChain.
Now when WebSecurity actually comes into play? WebSecurity actually allow us to customize springSecurityFilterChain(or FilterChainProxy). take a look at how springSecurityFilterChain is created.
It is the performBuild method of WebSecurity that is called for creating springSecurityFilterChain bean.
#Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this done by adding a #Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
As you can see when Spring wants to registers SecurityFilterChain into springSecurityFilterChain bean for each web.ignoring().... Spring is going to add a DefaultSecurityFilterChain which is a custom implementation of SecurityFilterChain into the beginning of the list.
When a request comes along springSecurityFilterChain is going to check its list of SecurityFilterChain in order to delegate the filtering job to that SecurityFilterChain. springSecurityFilterChain is going to call match method of each SecurityFilterChain. if the request URL starts with "/resources/**" in your case Spring delegates the job of filtering request to an instance of DefaultSecurityFilterChain in the beginning of the list and our custom SecurityFilterChains which is added by this line:
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
completely ignored.
configure(HttpSecurity) : It allows configuring web based security for specific http requests. It is used for configuration of web based security at a resource level, based on a selection match.
configure (WebSecurity) : Allows adding RequestMatcher instances that Spring Security should ignore.
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 would like to protect just a single URL, while allowing anonymous access for everything else.
The Java configuration examples i'm seeing in the internet all seem to indicate that you need to explicitly permitAll each and every URL, and appropriate hasRole for URLs that need to be protected. This in my case, creates a really unwieldy java code which I have modify every time I add a new URL to the application. Is there an easier java configuration that I can use.
And note also that in my case, the URL i'm protecting is a sub-resource, say employee/me, I would like employee/list, etc., to be anonymously accessible.
If you're using Java Configuration, you can use something like following in your configure method:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/employee/me").authenticated()
.antMatchers("/**").permitAll();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/employee/me").authenticated()
.antMatchers("/**").permitAll();
}
My security config class (which inherits from WebSecurityConfigurerAdapter) has a method like the following.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/restaurant/**").access("hasRole('ROLE_USER')")
.and()
.formLogin();
}
However I'd rather use #PreAuthorize on my controllers instead. If I remove the method everything requires auth. What should my method look like so everything is available and access is only determined by PreAuthorize?
As has been already stated, it is not very common to use method level security to secure controller methods but rather to secure methods with business logic. And even if you need to perform authorization based on request attributes, it should be possible to achieve this with URL based security and web security expressions.
Available expressions are defined by WebSecurityExpressionRoot class, an instance of which is used as the expression root object when evaluation web-access expressions. This object also directly exposed the HttpServletRequest object under the name request so you can invoke the request directly in an expression.
Here you can find more details on when to use URL based security and when method level security.
It is rather uncommon to use #PreAuthorize on controller methods, but there may me use cases, if the decision depends on request parameters ...
If you do not want to do any authorization at the request level, you can simply have :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin();
}
You only declare a form login, and no request security. But do not forget that request security uses less resources than method security.
Instead of .access("hasRole('ROLE_USER')"), try .access("permitAll"). Note that for request mappings that doesn't have a #PreAuthorize, everyone will be given access.