Throw custom exception from AuthenticationProvider to client(spring security) - spring-boot

I develop a spring boot REST service. I use #ControllerAdvice for exception catching. Also, I have a custom AuthenticationProvider and check a license in it.
#Component
public class MyAuthenticationProvider implements AuthenticationProvider {
private final LicenseService licenseService;
public MyAuthenticationProvider(LicenseService licenseService) {
this.licenseService = licenseService;
}
#Override
public boolean supports(Class<?> authentication) {
//...some code
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
try {
//...some code
licenseService.checkLicense(userDetails.getSomeId(), LicenseCode.FOO);
//..some code
} catch (LicenseException error) {
throw new AccessDeniedException(error.getMessage());
}
}
}
My licenseService throws LicenseException if the license does not exist or incorrect. I catch it and wrap to AccessDeniedException
at first, I wanted to catch LicenseException in #ControllerAdvice but quickly understood that it could be wrong. #ControllerAdvice catches exceptions in the controller layer.
That is why I wrap my exception to AccessDeniedException. But I want another logic: I want to throw a custom exception to the frontend. The frontend must understand this exception and show a special dialog to the client(License is required... bla-bla-bla). But I don't know how to do it on this step(AuthenticationProvider)

Your Controller catching the exception from your AuthenticationProvider would send a ResponseEntity with HttpStatus.FORBIDDEN to your client. You can wrap your exception or an errormessage in the body of the HTTP response.

Related

Spring Security - How to handle a RuntimeException in a custom AuthenticationFilter?

Using Spring Security, I have created a custom UsernamePasswordAuthenticationFilter. In this filter's attemptAuthentication method, I would like to retrieve the body of the HttpServletRequest, since credentials should be passed inside the body instead of request parameters.
I think I have found a good way to achieve this, but I am unsure about how to handle the IOException that could now occur inside this method. I have to catch the IOException inside this method, since the original method, which I override, does not throw an IOException.
This is my implementation:
#RequiredArgsConstructor
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
UserDTO user = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
return authenticationManager.authenticate(authenticationToken);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
My IDE suggests to throw a custom exception instead of a RuntimeException. But since this filter is part of the Spring Security filter chain, I am unsure about what should happen in case of an IOException.

Spring Authorization Server 0.2.2, how to disable a default authentication provider like (OAuth2TokenRevocation) and override it with a custom one?

I am using the new Spring Authorization Server 0.2.2 and I want to change the logic of the OAuth2TokenRevocationAuthenticationProvider and make my own implementation for the Token Revocation endpoint.
I added a new CustomRevocationAuthenticationProvider
public class CustomRevocationAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//My implementation
try {
//My implementation
} catch (Exception e) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
}
//My implementation
}
#Override
public boolean supports(Class<?> authentication) {
return OAuth2TokenRevocationAuthenticationToken.class.isAssignableFrom(authentication);
}
and I added this provider to the SecurityFilterChain like this:
#Bean
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults())
.authenticationProvider(new CustomRevocationAuthenticationProvider())
.build();
}
It works good but when I throw a OAuth2AuthenticationException in my implementation, the default OAuth2TokenRevocationAuthenticationProvider get executed and return 200 OK response.
is there any way to disable the default oauth2 provider from handling my exception and getting executed?
Great question. Since we're working on reference documentation, this is a good topic and I'll make a note to cover it in the configuration overview.
Take a look at OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http). When customizing Spring Authorization Server, you will typically need to copy that code and use the configurer directly. Here's an example:
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer.tokenRevocationEndpoint(tokenRevocationEndpoint -> tokenRevocationEndpoint
.authenticationProvider(new CustomRevocationAuthenticationProvider())
);
// ...
http.apply(authorizationServerConfigurer);

How to intercept not authorazied request to a rest controller (method level authorization) - Spring Boot

In a REST Controller I have the following method.
#GetMapping("/activate_user")
#RolesAllowed({Role.ROLE_ADMIN})
public void activateUser() {
// Some code here
}
If a user with ROLE_ADMIN calls this method, it works like it should.
If a user without ROLE_ADMIN calls this method, it return an Http-Status 403. That is also ok, but I want now to intercept this call in case the user is not authorized, run some custom code and return some JSON data back to the caller.
I don't know how it could be done with Spring?!
You can override the accessdenied exception and this way it will only be executed for 403 unauthorized.
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.getWriter().write("Custom Access Denied Message");
}
you can use MVC Interceptor Configuration to intercept specific URLs/APIs
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/adminRole/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/auth/*", "/ui/**", "/xyz/**");
}
}
You can even exclude specific URL's.

x509 validation fails before it can be captured

I have a Spring Boot application, using x509 authentication which further validates users against a database. When a user accesses the site, internal Spring code calls the loadUserByUsername method which in turn makes the database call. This all happens before the controller is aware anything is happening. If the user is not found it throws an EntityNotFoundException and displays the stack trace on the user's browser.
I'm using Spring Boot Starter. The controller has code to capture the exception and return a 'Not Authorized' message, but this happens long before. Has anyone else seen this and do you have a workaround?
#Service
public class UserService implements UserDetailsService {
public UserDetails loadUserByUsername(String dn) {
ApprovedUser details = auService.getOne(dn);
if (details == null){
String message = "User not authorized: " + dn;
throw new UsernameNotFoundException(message);
}
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
if (details.isAdminUser()){
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN_USER"));
}
return new AppUser(dn, "", authorities);
}
Usually, you would use a AuthenticationFailureHandler to encapsulate logic that's triggered by an AuthenticationException. The X509AuthenticationFilter would usually call PreAuthenticatedAuthenticationProvider to authenticate, which would in turn invoke the loadUserByUsername(...) method from UserDetailsService. Any AuthenticationException thrown by the UserDetailsService is caught by the filter and control is passed to the registered AuthenticationFailureHandler. This includes UsernameNotFoundException.
However, if you're using the X509Configurer, (http.x509()) there is no way of setting a handler directly on the filter. So once the exception is thrown, X509AuthenticationFilter catches it, sees that there's no default handler, and then simply passes the request to the next filter in the filter chain.
One way to get around this could be to provide a custom X509AuthenticationFilter.
In WebSecurityConfigurerAdapter:
#Autowired
private AuthenticationFailureHandler customFailureHandler;
#Autowired
private UserService customUserService;
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
protected void configure(HttpSecurity http) throws Exception {
...
http.x509().x509AuthenticationFilter(myX509Filter())
.userDetailsService(customUserService)
...
}
private X509AuthenticationFilter myX509Filter() {
X509AuthenticationFilter myCustomFilter = new X509AuthenticationFilter();
myCustomFilter.setAuthenticationManager(authenticationManagerBean());
...
myCustomFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
myCustomFilter.setAuthenticationFailureHandler(customFailureHandler);
return myCustomFilter;
}
Then you can write your own AuthenticationFailureHandler implementation and expose it as a bean.

dynamically add param to userAuthorizationUri in oauth2

Sometimes user's refresh token in local DB becomes stale. To replenish I'm trying to add prompt=consent param while making the oauth2 call. I was trying to #Autowire AuthorizationCodeAccessTokenProvider in my config class and in the afterPropertiesSet I was doing a setTokenRequestEnhancer and then realized that this bean is not even initialized via spring container when i looked the following code in OAuth2RestTemplate
private AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider> asList(
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
Searched if any spring code is calling org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.setAuthorizationRequestEnhancer(RequestEnhancer) to learn how to access it, but no one is calling it.
Question: How to dynamically add a param to userAuthorizationUri while making oauth2 call?
Unfortunately, I haven't found an elegant solution neither. I have noticed, however, that redirect is triggered by UserRedirectRequiredException.
I was able to dynamically add request params by registering custom filter that modifies this exception on the fly.
#Component
#Order(-102)
public class EnhanceUserRedirectFilter implements Filter {
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (final UserRedirectRequiredException ex) {
ex.getRequestParams().put("prompt", "consent");
throw ex;
}
}
#Override
public void destroy() {
}
}
Please note, such servlet filter has to have higher precedence than Spring Security. In my case, -102 is higher precedence than Spring Security default of -100.

Resources