Issue
I have an application using Spring Security 4.2.18 with the security configured in xml. Right now I am introducing Spring Boot 2.5.4 in the project. Since upgrading, I have a problem with the configuration of security for some requests.
I have a block matching specific requests and one matching the rest of all requests.
<http pattern="/api/**" use-expressions="true" authentication-manager-ref="apiAuthenticationManager" >
<http-basic/>
<intercept-url pattern="/api/**" access="hasRole('ROLE_API')"/>
<csrf disabled="true"/>
</http>
<http pattern="/**" auto-config="true" use-expressions="true" create-session="always" disable-url-rewriting="true"
authentication-manager-ref="authenticationManager">
<form-login login-processing-url="/resources/j_spring_security_check" login-page="/login"
username-parameter="j_username"
password-parameter="j_password"
authentication-failure-url="/login?login_error=t" default-target-url="/redirectlogin"/>
<logout logout-url="/resources/j_spring_security_logout"/>
...
<intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
...
</http>
<authentication-manager id="apiAuthenticationManager">
<authentication-provider user-service-ref="apiUserDetailsService">
</authentication-provider>
</authentication-manager>
The ApiUserDetailsService
follows the specification:
@Service
@Transactional
public class ApiUserDetailsService implements UserDetailsService {
...
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
boolean foundAccount = apiConfig.getUsername().equals(username);
if (foundAccount) {
return new User(username, apiConfig.getPassword(), singletonList(new SimpleGrantedAuthority("ROLE_API")));
}
throw new UsernameNotFoundException("Could not finder user with name " + username);
}
}
If I make a request to something under /api
and use incorrect Basic auth credentials, I previously got a 401 Unauthorized response. Since upgrading I get redirected to /login
and end up in a redirect loop since the credentials are used there as well.
If I remove the <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')"/>
in the second <http>
block, I get the expected behaviour.
How can I configure my application so that my request doesn't try to authorize using the rules in the second <http>
block? I have tried using an EntryPoint
but it isn't called before the user is erroneously authorized using the method of the generic <http>
block.
Solution
The problem wasn't that the configurations were overlapping but rather that the failed authentication was redirected to /login
which in turn was handled by the second <http>
configuration.
I solved it by creating an implementation of AuthenticationEntryPoint
that sets the 401 Unathorized
status on the response:
@Component
public class BasicAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
and in turn referencing to this in my basic authentication configuration:
<http pattern="/api/**" use-expressions="true" authentication-manager-ref="apiAuthenticationManager" >
<http-basic entry-point-ref="basicAuthenticationEntryPoint"/>
<intercept-url pattern="/api/**" access="hasRole('ROLE_API')"/>
<csrf disabled="true"/>
</http>
Answered By - David Göransson