I am working on this override below and even though I login with proper credentials I keep getting 403 or 500 errors. My filter ordering is correct as it is the same as our other apps so I guess I am wrong in the anyRequest() part that I am missing something. How do I get my credentials to persist?
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest().hasAnyRole("read_only", "read_write", "read_write_delete")
.and()
.addFilter(securityContextPersistenceFilter())
.addFilterAfter(preAuthFilter(), SecurityContextPersistenceFilter.class)
.addFilterAfter(ssoFilter(), PreAuthFilter.class)
.addFilterAfter(new RequestContextFilter(), SSOFilter.class)
.addFilterAfter(new CrossFramePreventionFilter(), RequestContextFilter.class)
.addFilterAfter(characterEncodingFilter, CrossFramePreventionFilter.class)
.addFilterAfter(exceptionTranslationFilter(), CharacterEncodingFilter.class)
.addFilterAfter(new NoCacheFilter(), ExceptionTranslationFilter.class);
}
The reason for the failure is because order matters in the filter chain and I had the preAuthFilter listed before the ssoFilter. The ssoFilter sets and removes UserPrincipal.authenticatedUser which the preAuth filter uses to create an authentication token. Since I had preAuthFilter first the authenticated user was null and therefore token creation failed. Working its way through the filter chain if there still is no token created then spring automatically adds an AnonymousAuthenticationFilter which creates an AnonymousAuthenticationToken with "anonymousUser" and "ROLE_ANONYMOUS" like i saw in logs.
Reversing the order of those filters allowed for a valid PreAuthenticatedAuthenticationToken to be created and then everything worked as expected.
Related
I have a spring restful application, backend - Spring 2.4.3, frontend - Angular, when I trying to restrict access to individual pages, I get 401 code. I've tried all variations of hasRole () and hasAuthority () nothing helps. What am I doing wrong?
SecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login", "/registration").permitAll()
.antMatchers("/profile","/profile/*").hasAnyAuthority("USER","ADMIN","INTERVIEWER")
.antMatchers("/getAllUsers").permitAll()
.anyRequest().authenticated();
http
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
/*.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class)*/
.cors();
}
Role.java
#XmlType
#XmlEnum
public enum Role implements GrantedAuthority {
ADMIN,
USER,
INTERVIEWER;
#Override
public String getAuthority() {
return this.name();
}
}
Result:
something wrong :(
Based on your provided code, the line .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class) is commented out. I can't speak to what happens when you un-comment that line (since it is a custom filter), but without that line, you have no means of authenticating. This results in your entry point (which is not provided in your example) being invoked, and seems to be returning your 401 status code.
You can test this by commenting out the lines:
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
and adding .formLogin().and() instead. Form Login will provide a default authentication entry point, default authentication filter, and (if using spring boot) default user details service with a randomly generated password printed to your console, which you can use to test logging in. See the docs for more info on this.
A note on testing with hello world (out of the box) configuration: It is a very useful technique to use formLogin() for testing authorization rules (e.g. .antMatchers("/profile","/profile/*").hasAnyAuthority("USER","ADMIN","INTERVIEWER")) in Spring Security. It allows you to eliminate your authentication mechanism from being the problem. Once you are confident your authorization rules are working, you can move on to configuring your own authentication scheme. When possible, seek to utilize an existing scheme provided by Spring Security, and only create a custom filter when you cannot use an out of the box scheme. You can read about JWT authentication in the docs.
I know there is a lot of questions like this, but I could not find an answer which solves my case.
Here is my config:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout().permitAll();
}
}
And here is my endpoint I want users to have access without logging in:
#Slf4j
#RestController
public class MyController {
#PostMapping(value = "/", consumes = MediaType.TEXT_PLAIN_VALUE)
public void acceptAnonymously(HttpEntity<String> requestEntity) {
log.debug("body: {}", requestEntity.getBody());
}
}
So basically, I want to allow making unauthenticated POST requests to localhost:8080. Everything else should be authenticated. But when I hit localhost:8080 with postman, this is what I get:
So, CSRF stands for Cross-Site Request Forgery and I believe is enabled by default with Spring Web/Security. When it is enabled, you need to properly pass the correct csrf token to your app in order to access your application otherwise you will get thrown a 403 forbidden type error. Alternatively, there are other means of authenticating users if you so desired.
.csrf().disable()
Actually disabling CSRF is not a good idea for all situations. CSRF is enabled by default and as a result, the CSRF token is added to the HttpServletRequest attribute named _csrf. So you only need to add it to your requests.
If you are using Thymeleaf for your html templates you could add something like this to your forms:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
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.
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.
Just want to see whether I'm interpreting the answer to this question the right way.
If we only need to secure one path like this:
http.antMatcher("/api/**").authorizeRequests()....
Then use antMatcher().
If we need to secure multiple URL paths like this:
http
.authorizeRequests()
.antMatchers("/high_level_url_A/sub_level_1").hasRole('USER')
.antMatchers("/high_level_url_A/sub_level_2").hasRole('USER2')
...
Then use antMatchers().
There are two answers in this question, but the example provided in each of them contradicts example given in the other. The first answer says that the author does not need antMatcher() and the second says to always start with `antMatcher() IIUC.
HttpSecurity.antMatcher() changes the default request matcher for the HttpSecurity instance to an AntPathRequestMatcher from AnyRequestMatcher. ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry.antMatchers() is used for applying authorization rules to a subset of endpoints associated with the current HttpSecurity instance.
Example code:
http
.antMatcher("/api/**")
.httpBasic()
.disable()
.authorizeRequests()
.antMatchers("/api/user/**", "/api/ticket/**", "/index")
.hasRole("USER");
In the example above, basic authorization is disabled for all endpoints matching /api/**. Additionally, endpoints matching /api/user/** or /api/ticket/** will require the request's Authentication to contain ROLE_USER. However, when a user attempts to access /index, they will be met with a basic auth prompt. Upon entering credentials, the user will be granted access to the endpoint regardless of whether or not the request's Authentication contains ROLE_USER. This is because .antMatcher("/api/**") is limiting the scope of the entire HttpSecurity instance to that specific AntMatcher.
The example below would ensure that the HttpSecurity's scope includes the three previous AntMatchers and nothing else:
http
.requestMatchers()
.antMatchers("/api/user/**", "/api/ticket/**", "/index")
.and()
.httpBasic()
.disable()
.authorizeRequests()
.any()
.hasRole("USER");
EDIT
If you use #hasRole(), then your role should not start with "ROLE_" as this is automatically inserted.
antMatcher() allows configuring the HttpSecurity to only be invoked when matching the provided ant pattern.
If more advanced configuration is necessary, consider using requestMatchers() or requestMatcher(RequestMatcher).
Invoking antMatcher() will override previous invocations of antMatcher(), mvcMatcher(), requestMatchers(), regexMatcher(), and requestMatcher()
See the example bellow for using requestMatchers
#Configuration
#EnableWebSecurity
public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers((requestMatchers) ->
requestMatchers
.antMatchers("/api/**")
.antMatchers("/oauth/**")
)
.authorizeRequests((authorizeRequests) ->
authorizeRequests
.antMatchers("/**").hasRole("USER")
)
.httpBasic(withDefaults());
}
}
The configuration below is also the same as the above configuration.
#Configuration
#EnableWebSecurity
public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers((requestMatchers) ->
requestMatchers
.antMatchers("/api/**")
)
.requestMatchers((requestMatchers) ->
requestMatchers
.antMatchers("/oauth/**")
)
.authorizeRequests((authorizeRequests) ->
authorizeRequests
.antMatchers("/**").hasRole("USER")
)
.httpBasic(withDefaults());
}
}