Spring Security: Why is my custom AccessDecisionVoter not invoked - spring-mvc

I'm trying to do URL authorization using a custom AccessDecisionVoter. I don't get any errors and debugging shows that my voter is picked up at start up. However, at runtime, the vote method is not called, thus allowing every authenticated user full access.
Note that, I don't need method security. I'm also not using XML config. That rules out every example ever posted on the internet regarding this topic.
#Configuration
#EnableWebSecurity
#EnableWebMvc
#ComponentScan
#Order(-10)
public class HttpSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${trusted_ports}")
private List<Integer> trustedPorts;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private ServiceIdAwareVoter serviceIdAwareVoter;
RequestMatcher requestMatcher = new OrRequestMatcher(
// #formatter:off
new AntPathRequestMatcher("/**", GET.name()),
new AntPathRequestMatcher("/**", POST.name()),
new AntPathRequestMatcher("/**", DELETE.name()),
new AntPathRequestMatcher("/**", PATCH.name()),
new AntPathRequestMatcher("/**", PUT.name())
// #formatter:on
);
#Override
protected UserDetailsService userDetailsService() {
return userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(preAuthProvider());
auth.authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.
httpBasic().and().
authorizeRequests().anyRequest().fullyAuthenticated().
accessDecisionManager(accessDecisionManager()).and().
csrf().disable().
logout().disable().
exceptionHandling().and().
sessionManagement().sessionCreationPolicy(STATELESS).and().
anonymous().disable().
addFilterAfter(preAuthFilter(), X509AuthenticationFilter.class).
addFilter(authFilter());
// #formatter:on
}
AccessDecisionManager accessDecisionManager() {
return new UnanimousBased(ImmutableList.of(serviceIdAwareVoter));
}
Filter preAuthFilter() throws Exception {
PreAuthenticationFilter preAuthFilter = new PreAuthenticationFilter(trustedPorts);
preAuthFilter.setAuthenticationManager(super.authenticationManager());
return preAuthFilter;
}
PreAuthenticatedAuthenticationProvider preAuthProvider() {
PreAuthenticatedAuthenticationProvider preAuthProvider = new PreAuthenticatedAuthenticationProvider();
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper = new UserDetailsByNameServiceWrapper<>();
userDetailsServiceWrapper.setUserDetailsService(userDetailsService());
preAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper);
return preAuthProvider;
}
Filter authFilter() throws Exception {
AppIdAppKeyAuthenticationFilter authFilter = new AppIdAppKeyAuthenticationFilter(requestMatcher);
authFilter.setAuthenticationFailureHandler(new ExceptionStoringAuthenticationFailureHandler());
authFilter.setAuthenticationSuccessHandler(new UrlForwardingAuthenticationSuccessHandler());
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}
AuthenticationProvider authProvider() {
AppIdAppKeyAuthenticationProvider authProvider = new AppIdAppKeyAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
return authProvider;
}

Background:
After hours of debugging, I found out the root cause of the problem, which is really deep. Part of it is due to the fact that the Spring Security Java config is very poorly documented (for which I've opened a JIRA ticket). Theirs, as well as most online, examples are copy-pasted from XML config whereas the world has stopped using Spring XML config since probably 2010. Another part is due to the fact that REST service security is an afterthought in the Spring Security design and they don't have first-class support for protecting applications that don't have a login page, error page and the usual view layer. Last but not the least is that there were several (mis)configurations in my app which all came together and created a perfect storm of mind-boggling complexity.
Technical Context:
Using the authorizeRequests() configures a ExpressionUrlAuthorizationConfigurer which ultimately sets up a UnanimousBased AccessDecisionManager with a WebExpressionVoter. This AccessDecisionManager is called from the FilterSecurityInterceptor if the authentication succeeds (obviously there's no point in authorization if the user fails authentication in the first place).
Issues:
In my AbstractAnnotationConfigDispatcherServletInitializer subclass, which is basically the Java version of the web.xml, I'd configured filters not to intercept forward requests. I'm not going to go into the why here. For the interested, here's an example of how it's done:
private Dynamic registerCorsFilter(ServletContext ctx) {
Dynamic registration = ctx.addFilter("CorsFilter", CorsFilter.class);
registration.addMappingForUrlPatterns(getDispatcherTypes(), false, "/*");
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ? EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC)
: EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
If you take the DispatcherType.FORWARD out of the dispatcher types set, the registered filter doesn't kick in for that kind of request.
The authFilter shown in my question extended from UsernamePasswordAuthenticationFilter and had an AuthenticationSuccessHandler which forwarded the request to the destination URL after successful authentication. The default Spring implementation uses a SavedRequestAwareAuthenticationSuccessHandler which does a redirect to a webpage, which is unwanted in the context of a REST app.
Due to the above 2 reasons, the FilterSecurityInterceptor was not invoked after successful authentication which in turn, skipped the authorization chain causing the issue in my original post.
Fix:
Get rid of custom dispatcher configuration from web app initializer.
Don't do forward, or redirect, from AuthenticationSuccessHandler. Just let the request take it's natural course.
The custom voter has a vote method that looks as follows:
public int vote(Authentication authentication, FilterInvocation fi,
Collection<ConfigAttribute> attributes) {
}
The attributes in my case, as shown in my original post, is the string expression fullyAuthenticated. I didn't use it for authorization as I already knew the user to have been authenticated through the various filters in the authentication flow.
I hope this serves as documentation for all those souls who're suffering from the lack of documentation in Spring Security Java config.

Your config is saying that you are allowing access to fully authenticated users right here:
authorizeRequests().anyRequest().fullyAuthenticated().
You are telling Spring Security to grant access to any request as long as they are fully authenticated. What's you're goal? How are you trying to restrict access, by a role/permission? I'm guessing it's something that you are dictating inside your custom voter bean?
Usually the voter bean comes into play when you have conflicting security levels, for example, here you say that that all requests have full access but if your code hits a method with method level security like this (not a very real-world example):
#PreAuthrorize("permitNone")
public void someMethod{
...
}
You're going to have voters come into play because your java security config is saying "grant access to everyone" (voting yes to access) but this method annotation is "grant access to no one" (voting no to access).
In your case, there's nothing to vote on, you are granting everyone access.

Related

Why do I get null `Authentication` as #Controller method parameter in `#WebMvcTest`?

The context: I created a test annotation #WithMockAuthentication to populate test security context with an Authentication instance, much like #WithMockUser does.
The main difference being, in my case, the instance is a Mockito mock.
What I experience: As soon as I replace an actual instance with a mock, the Authentication instance provided as controller method parameter is null in annotated tests: in the WithSecurityContextFactory, if I replace:
public Authentication workingAuthentication(WithMockAuthentication annotation) {
return new TestAuthentication(annotation.name(), Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
}
with
public Authentication bogousAuthentication(WithMockAuthentication annotation) {
var auth = mock(Authentication.class);
when(auth.getName()).thenReturn(annotation.name());
when(auth.getAuthorities()).thenReturn((Collection) Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
when(auth.isAuthenticated()).thenReturn(true);
return auth;
}
Then I get NPE in the controller tests at
#RequestMapping("/method")
#PreAuthorize("hasRole('ROLE_AUTHORIZED')")
public ResponseEntity<String> securedMethod(Authentication auth) {
// Here, auth is null if Authentication is a mock
return ResponseEntity.ok(String.format("Hey %s, how are you?", auth.getName()));
}
I've created a minimal sample to reproduce. Run the test to see the failure.
I'm pretty sure I face a bug. Enough to create an issue in spring-security project, but it seems that Spring team team has no time to investigate...
[EDIT]
This last statement is uselessly offensive and completely wrong as the answer is provided by Rob Winch, who is a major member of spring-security :/ My Bad
The argument for your SampleController is of type Authentication which is an instance of Principal and thus the ServletRequestMethodArgumentResolver will attempt to resolve the argument from HttpServletRequest.getUserPrincipal().
The mock that you are creating did not stub the Authentication.getPrincipal() method.
public Authentication bogousAuthentication(WithMockAuthentication annotation) {
var auth = mock(Authentication.class);
when(auth.getName()).thenReturn(annotation.name());
when(auth.getAuthorities()).thenReturn((Collection) Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
when(auth.isAuthenticated()).thenReturn(true);
return auth;
}
For that reason, Authentication.getPrincipal() is null and thus SecurityContextHolderAwareRequestWrapper.getUserPrincipal() returns null. Why does it return null when the principal is null? I cannot be certain the original intention as the code was added before I was a team member. However, it makes sense in Spring Security's model. Authentication can represent both an authenticated user and credentials used for authenticating. The Javadoc of Authentication.getPrincipal() states (emphasis mine):
The identity of the principal being authenticated. In the case of an
authentication request with username and password, this would be the
username. Callers are expected to populate the principal for an
authentication request.
The AuthenticationManager implementation will often return an
Authentication containing richer information as the principal for use
by the application. Many of the authentication providers will create a
UserDetails object as the principal.
The null check is to ensure that the Authentication is indeed representing an authenticated user.
To fix it you must stub out the getPrincipal() method with something like when(auth.getPrincipal()).thenReturn("bogus");. The change can be seen below and in my pull request.
public Authentication bogousAuthentication(WithMockAuthentication annotation) {
var auth = mock(Authentication.class);
when(auth.getPrincipal()).thenReturn("bogus");
when(auth.getName()).thenReturn(annotation.name());
when(auth.getAuthorities()).thenReturn((Collection) Stream.of(annotation.authorities()).map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
when(auth.isAuthenticated()).thenReturn(true);
return auth;
}

How to create a WebClient-object in a spring application with oauth2

I'm developing a spring application (client) that is secured with an OAuth2 provider. This application should do some REST calls to another spring application (resource server). For performing the REST calls, I will use spring's WebClient.
I therefore try to create a bean of type WebClient as can be found in several blogs.
#Configuration
public class AppConfig {
#Bean
public WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId("myprovider");
return WebClient.builder().filter(oauth).build();
}
}
When starting the application, I get the following error:
The following candidates were found but could not be injected:
- Bean method 'clientRegistrationRepository' in 'ReactiveOAuth2ClientAutoConfiguration' not loaded because NoneNestedConditions 1 matched 0 did not; NestedCondition on ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.ServletApplicationCondition found 'session' scope
Action:
Consider revisiting the entries above or defining a bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository' in your configuration.
As several websites recommend exactly this code for generating a WebClient instance when using OAuth2 authentication, I'm wondering what I'm doing wrong?
Do you have any suggestions for me?
Thanks.
I got the same issue. I changed the code as provided in the video : https://www.youtube.com/watch?v=1N-xwmoN83w&t=1569s and that worked
#Bean
public WebClient webClient(ClientRegistrationRepository clientRegistrationRepository , OAuth2AuthorizedClientRepository authorizedClientRepository) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServletOAuth2AuthorizedClientExchangeFilterFunction (clientRegistrationRepository , authorizedClientRepository);
return WebClient.builder().apply(oauth.oauth2Configuration()).build();
}
Hope that helps.

App Insights not using RequestTelemetryFilter for health check Controller in Spring Boot app

I have a Spring Boot app with a few Controllers I want to track their dependencies (including outbound Http requests). That all works as expected. However, I have one controller for a health check (returning 204) that I do not want telemetry for. All other responses mention custom code components, but according to the documentation, this should be doable within the AI-Agent.xml config.
<BuiltInProcessors>
<Processor type="RequestTelemetryFilter">
<Add name="NotNeededResponseCodes" value="204" />
</Processor>
</BuiltInProcessors>
I notice on the classpath that there are two RequestTelemtryFilter instances (one from ai-core and one from ai-web, neither of which get hit when i debug).
Configuring the Agent (via AI-Agent.xml) is different than configuring custom telemetry (via Applicationinsights.xml). Spring boot + the agent requires the use of a custom Telemetry Processor and pulling into your configuration via #Bean. No additional XML in the AI-Agent is necessary.
public class HealthCheckTelemetryFilter implements TelemetryProcessor
{
public HealthCheckTelemetryFilter()
{
// TODO Auto-generated constructor stub
}
#Override
public boolean process(Telemetry telemetry)
{
RequestTelemetry reqTel = (RequestTelemetry) telemetry;
if(reqTel.getResponseCode().equals(HttpStatus.NO_CONTENT.toString()))
return false;
else
return true;
}
}
NOTE: dont forget appropriate type check

How is the callback from a resource owner processed in HWIOAuthBundle?

I am trying to understand how HWIOauthBUndle works. I can see how the initial authorization request to a resource owner is built and made.
I do not see however, how a callback made from a resource owner triggers any controller/action in my application (which it most obviously does, though).
When following the generally available instructions, the callback will be made to something like <path to my app>/check-[resourceOwner], e.g. http://www.example.com/oauth/check-facebook.
In my routing.yml file, I put
facebook_login:
pattern: /oauth/check-facebook
I don't see how any controller is associated with that route, so what actually happens when a callback is made to my application?
The authentication provider system is one of the more complicated features. You will probably want to read through here: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
Callbacks are handled through a request listener. Specifically:
namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
class OAuthListener extends AbstractAuthenticationListener
{
public function requiresAuthentication(Request $request)
{
// Check if the route matches one of the check paths
foreach ($this->checkPaths as $checkPath) {
if ($this->httpUtils->checkRequestPath($request, $checkPath)) {
return true;
}
}
return false;
}
protected function attemptAuthentication(Request $request)
{
// Lots of good stuff here
How checkPaths get's initialized and how all the calls are made would require a very long explanation. But the authentication provider chapter will get you going.

WCF Client Proxies, Client/Channel Caching in ASP.Net - Code Review

long time ASP.Net interface developer being asked to learn WCF, looking for some education on more architecture related fronts - as its not my strong suit but I'm having to deal.
In our current ASMX world we adopted a model of creating ServiceManager static classes for our interaction with web services. We're starting to migrate to WCF, attempting to follow the same model. At first I was dealing with performance problems, but I've tweaked a bit and we're running smoothly now, but I'm questioning my tactics. Here's a simplified version (removed error handling, caching, object manipulation, etc.) of what we're doing:
public static class ContentManager
{
private static StoryManagerClient _clientProxy = null;
const string _contentServiceResourceCode = "StorySvc";
// FOR CACHING
const int _getStoriesTTL = 300;
private static Dictionary<string, GetStoriesCacheItem> _getStoriesCache = new Dictionary<string, GetStoriesCacheItem>();
private static ReaderWriterLockSlim _cacheLockStories = new ReaderWriterLockSlim();
public static Story[] GetStories(string categoryGuid)
{
// OMITTED - if category is cached and not expired, return from cache
// get endpoint address from FinderClient (ResourceManagement SVC)
UrlResource ur = FinderClient.GetUrlResource(_contentServiceResourceCode);
// Get proxy
StoryManagerClient svc = GetStoryServiceClient(ur.Url);
// create request params
GetStoriesRequest request = new GetStoriesRequest{}; // SIMPLIFIED
Manifest manifest = new Manifest{}; // SIMPLIFIED
// execute GetStories at WCF service
try
{
GetStoriesResponse response = svc.GetStories(manifest, request);
}
catch (Exception)
{
if (svc.State == CommunicationState.Faulted)
{
svc.Abort();
}
throw;
}
// OMITTED - do stuff with response, cache if needed
// return....
}
internal static StoryManagerClient GetStoryServiceClient(string endpointAddress)
{
if (_clientProxy == null)
_clientProxy = new StoryManagerClient(GetServiceBinding(_contentServiceResourceCode), new EndpointAddress(endpointAddress));
return _clientProxy;
}
public static Binding GetServiceBinding(string bindingSettingName)
{
// uses Finder service to load a binding object - our alternative to definition in web.config
}
public static void PreloadContentServiceClient()
{
// get finder location
UrlResource ur = FinderClient.GetUrlResource(_contentServiceResourceCode);
// preload proxy
GetStoryServiceClient(ur.Url);
}
}
We're running smoothly now with round-trip calls completing in the 100ms range. Creating the PreloadContentServiceClient() method and adding to our global.asax got that "first call" performance down to that same level. And you might want to know we're using the DataContractSerializer, and the "Add Service Reference" method.
I've done a lot of reading on static classes, singletons, shared data contract assemblies, how to use the ChannelFactory pattern and a whole bunch of other things that I could do to our usage model...admittedly, some of its gone over my head. And, like I said, we seem to be running smoothly. I know I'm not seeing the big picture, though. Can someone tell me what I've ended up here with regards to channel pooling, proxy failures, etc. and why I should head down the ChannelFactory path? My gut says to just do it, but my head can't comprehend why...
Thanks!
ChannelFactory is typically used when you aren't using Add Service Reference - you have the contract via a shared assembly not generated via a WSDL. Add Service Reference uses ClientBase which is essentially creating the WCF channel for you behind the scenes.
When you are dealing with REST-ful services, WebChannelFactory provides a service-client like interface based off the shared assembly contract. You can't use Add Service Reference if your service only supports a REST-ful endpoint binding.
The only difference to you is preference - do you need full access the channel for custom behaviors, bindings, etc. or does Add Service Reference + SOAP supply you with enough of an interface for your needs.

Resources