Since I have updated Spring Boot Version to 2.6.7 I get logs that the way I define unsecured routes is not recommended anymore.
Log message:
You are asking Spring Security to ignore Ant [pattern='/actuator/**']. This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.
The way I was describing the config that Spring Security has to ignore these patterns was done by defining a WebSecurityConfiguration and ignoring those routes. What happens in this case is, that the whole security chain is skipped and the above mentioned logs are written. It's okay for me, but not for Spring ;).
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/actuator/**"
);
}
}
When defining these route as part of the httpSecurity as mentioned in the log. The problem occurs that an expired/invalid token cause an error (401 unauthorized) as well for unsecured routes like /actuator/health.
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Getter
private String[] unsecuredPathList;
#PostConstruct
public void postConstruct() {
this.unsecuredPathList = new String[] {
"/actuator/**"};
}
#Bean
public JwtTokenStore jwtTokenStore() {
return new JwtTokenStore(new CustomJwtAccessTokenConverter(true));
}
#Override
public void configure(ResourceServerSecurityConfigurer resourceServer) throws Exception {
resourceServer.tokenStore(jwtTokenStore());
resourceServer.tokenExtractor(new SessionCookieTokenExtractor());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous()
.authorities("ANONYMOUS")
.and()
.authorizeRequests()
.antMatchers(unsecuredPathList)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}
The goal I want to reach is:
For an unsecure resource the token will not be evaluated/the result is ignored and there is no 401-Unauthorized-Error.
Is there anything I can do in httpSecurity?
Or is there any other recommended way to reach that goal?
Thanks in advance for your help.
There is a group of methods in HttpSecurity class, which allows you to apply defined security rules only to specific paths, and thus create different security filter chains with different rules for different urls.
For example, you can exclude some urls like this:
// convert your String array into a List of RequestMatcher
List<RequestMatcher> excludedPathMatchers = Arrays.stream(unsecuredPathList)
.map(AntPathRequestMatcher::new)
.collect(Collectors.toList());
// configure HttpSecurity to apply filter chain only on paths, that don't match any of the excluded paths
http.requestMatcher(new NegatedRequestMatcher(new OrRequestMatcher(excludedPathMatchers)));
Or you can write something like this, if you have only 1 unsecured endpoint:
http.requestMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/some_url/**")));
Related
I know that there are some similar topics but they are about implementation difficulties, whereas my question is more architect wise.
And it is generally not springframework related.
Let's say there is an application that implements both client\resource (in terms of OAuth2) behaviors.
Also it supports Basic auth with for testing purposes (ans it has its own set of static\ldap users ).
Auth provider is done as a separate application.
This "three-type" auth is reached by
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Basic"));
})
.authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
.httpBasic()
;
}
....
}
then goes
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final static Logger logger = LoggerFactory.getLogger(OAuth2ResourceServerConfig.class);
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(request -> {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Bearer"));
})
.authorizeRequests()
.anyRequest().authenticated();
}
....
}
and then
#Configuration
#EnableOAuth2Sso
#Order(4)
public class OAuth2SsoConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests()
.antMatchers("/", "/login**").permitAll()
.anyRequest().authenticated()
.and().csrf().disable()
;
}
....
}
all in one package (altogether with UI).
That works pretty well. But.
Is that good at all to have it like that?
Some systems which this app integrates with already have "client" behavior itself (like SalesForce), so UI and #EnableOAuth2Sso configuration seem to be dead weight.
Am I missing something in terms of security vulnerabilities? I was able to see that once one bearer token is accepted by app, it creates session and postman sends cookie back to the app on the next request and app manages this session even if another bearer token(for another user) has been applied to the Authorization header.
Does it make sense to customize this via maven profiles or via splitting it to the separate apps (pure UI&client and Resource API)?
Three options as I see it:
Thanks.
I have an app that uses both Basic Auth and OAuth2.
Some URLs are authorized using Basic Auth and "/api/**" is authorized using OAuth2.
Currently, I have two Java config files (WebSecurityConfigurerAdapter and ResourceServerConfigurerAdapter)
Each of the config files define a public void configure(HttpSecurity http) method.
The trouble I'm having is that I need an elegant way to tell my app whether to use basic auth or oauth2 given the url request.
Currently I'm using requestMatchers to make this happen:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.csrf().disable()
.requestMatchers()
.antMatchers("/*", "/login/**", "/reviews/**")
.and()
.authorizeRequests()
.antMatchers("/*").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/img/**").permitAll()
.formLogin()
.loginPage("/login")
.successHandler(loginSuccessPostHandler)
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/").permitAll()
.and()
.apply(getSpringSocialConfigurer());
}
}
#Configuration
public class OAuth2ServerConfig
{
#Configuration
#EnableResourceServer
protected static class Oauth2ServerConfig extends ResourceServerConfigurerAdapter
{
#Override
public void configure(HttpSecurity http) throws Exception
{
http.httpBasic().disable();
http.csrf().disable();
http.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers("/api/v1/**").access("#oauth2.hasScope('read')");
}
}
}
The problem is that every time I add a new URL that's NOT "/api/**", I'll need to add it into my WebSecurityConfig's requestMatcher section... this could lead to silly bugs in the future.
Is there a way to have a requestMatcher search based on a negative lookahead regex? I tried this using the regex: ^(?!/api) but since it doesn't actually return a MATCH and only returns a "find == true", it doesn't seem to get the job done.
Any thoughts / suggestions?
You can use NegatedRequestMatcher:
A RequestMatcher that will negate the RequestMatcher passed in. For example, if the RequestMatcher passed in returns true, NegatedRequestMatcher will return false. If the RequestMatcher passed in returns false, NegatedRequestMatcher will return true.
You should use Order(...) annotation on #Configuration classes. Make your OAuth2ServerConfig config first and serving only http.requestMatchers().antMatchers("/api/**") and make your WebSecurityConfig second (#Order(2)) without http.requestMatchers() to serve all rest URLs!
See details on https://stackoverflow.com/a/44871933/173149
I'm configuring HttpSecurity for a Spring Boot rest server, and I need to make the create user end point not require authentication.
The mapping for the controller method is POST /users/{username}?action=create
I produced the following regex which I tested with online tools to make sure it matched correctly:
(\/users\/)([^\/]+)(\?action=create)
My only rule for usernames was that they cannot contain /, and so I believe that regex fufills that.
However, despite adding the following to the httpsecurity config:
.authorizeRequests()
.regexMatchers(HttpMethod.POST,"(\\/users\\/)([^\\/]+)(\\?action=create)")
.permitAll()
I am still unable to hit my endpoint and am unsure why.
Thanks!
UPDATE:
Apparently my custom filters would be applied unless I configured the WebSecurity object to ignore it completely, like so:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**")
.and().ignoring().regexMatchers(HttpMethod.POST, "(\\/users\\/)([^\\/]+)(\\?action=create)");
}
But now spring is complaining about not being able to find an authentication object...
My original solution was authorizing requests that had been authenticated, the following makes it so ALL requests (anonymous or not) are good to go!
Add this to your custom WebSecurityConfigurerAdapter
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**")
.and().ignoring().regexMatchers(HttpMethod.POST, "(\\/users\\/)([^\\/]+)(\\?action=create)");
}
And just for clarity, this is the controller method it is applied to:
#RequestMapping(value = "/users/{username}",params = {"action="+Action.CREATE}, method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public UserModel createUser(#PathVariable(value="username") String username, #RequestBody UserModel user) {
user.setUsername(username);
return userService.createUser(user);
}
EDIT:
I further drilled down the problem and turns out issue persists even with single configuration. If I use single configuration and keep
http.antMatcher("/api/test/**")
urls don't get secured.
Removing the antMatcher and antMatchers immediately secures the url.
i.e if I use:
http.httpBasic()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
then only spring security is securing url. Why isn't antMatcher functioning?
(Updated the title to include actual issue.)
Original Post:
I have referred following stackoverflow questions:
Spring REST security - Secure different URLs differently
Using multiple WebSecurityConfigurerAdapter with different AuthenticationProviders (basic auth for API and LDAP for web app)
and spring security doc:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#multiple-httpsecurity
But I am not able to configure multiple http security elements.
When I follow the official spring doc, it works in my case only becuase of the fact that the second http security element is a catch-all, but as soon as I add a specific url, all the urls can be accessed without any authentication.
Here's my code:
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
Now any url can be accessed. If I remove antMatcher from second configuration, all the urls become secured.
The pattern must not contain the context path, see AntPathRequestMatcher:
Matcher which compares a pre-defined ant-style pattern against the URL ( servletPath + pathInfo) of an HttpServletRequest.
and HttpServletRequest.html#getServletPath:
Returns the part of this request's URL that calls the servlet. This path starts with a "/" character and includes either the servlet name or a path to the servlet, but does not include any extra path information or a query string. Same as the value of the CGI variable SCRIPT_NAME.
and HttpServletRequest.html#getContextPath:
Returns the portion of the request URI that indicates the context of the request. The context path always comes first in a request URI. The path starts with a "/" character but does not end with a "/" character. For servlets in the default (root) context, this method returns "". The container does not decode this string.
Your modified and simplified code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/test/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
I have a set a custom authentication filter in my Spring 4 MVC + Security + Boot project. The filter does it's job well and now I want to disable the security for some URI (like /api/**). Here is my configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity.ignoring().antMatchers("/api/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(filter, BasicAuthenticationFilter.class);
}
}
Unfortunately, when I call a resource under /api/... the filter is still chained. I've added println in my filter and it's written to the console on every call. Do you know what's wrong with my configuration?
UPDATE
Filter code:
#Component
public class EAccessAuthenticationFilter extends RequestHeaderAuthenticationFilter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("FILTER");
if(SecurityContextHolder.getContext().getAuthentication() == null){
//Do my authentication stuff
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(user, credential, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
super.doFilter(request, response, chain);
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
remove #Component on class EAccessAuthenticationFilter,and like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new EAccessAuthenticationFilter(), BasicAuthenticationFilter.class);
}
https://github.com/spring-projects/spring-security/issues/3958
I don't have enough reputation to add a comment, but for anyone like me who was looking for a little more of an explanation for kimhom's answer, WebSecurityConfigurerAdapter will tell Spring Security to ignore any filters added through it. The filter was then still being invoked because the #Component (or any flavor of #Bean) annotation told Spring to add the filter (again) outside of the security chain. So while the filter was being ignored in the security chain, it was not being ignored by the other (non-security?) chain.
This solved two weeks of headaches for me. In my case my custom filter needed the Authentication object given by the SecurityContext where it kept coming up as null because the security chain was never executed.
I had the correct configuration to ignore some context path in the web security configuration as below..
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v1/api1").antMatchers("/v1/api2");
}
But I mistakenly had added #PreAuthorize(...) on my controller method and it seems like that method level security was overriding any security configuration set up at the start.
After few tests I realized that in fact my configurations are ok and it's just a comprehension problem. The spring.security.ignored=/api/** doesn't bypass or turn off the filter. In reality every request still pass through my custom filter, but the difference is that Spring Security doesn't mind of the authentication status nor the granted authority coming from the custom filter.
I was wondering that the "ignored" property simply bypass the spring security filters. It sounds like I was totally wrong...
I always found the easiest way to do this is to put this configuration in your application.properties:
security.ignored=/api/**
I think you also need it in the Filter class as well (extends RequestHeaderAuthenticationFilter) i.e.
public class EAccessAuthenticationFilter extends RequestHeaderAuthenticationFilter {
public EAccessAuthenticationFilter() {
super(new RequestMatcher() {
RequestMatcher matcher = new AntPathRequestMatcher("/v1/api1");
return matcher.matches(request);
});
}
}