io.undertow.servlet.util.IteratorEnumeration cannot be cast to java.lang.String - spring-boot

I'm trying to authenticate a user from the API key sent in the header without any user details through login. I then get a casting exception because I try to get the principal cast into a String.
I have tried to get the header within the SecurityConfig class but that hasn't worked. I've also tried getting it within the custom filter which is how the current solution sits anyway.
#Bean
public APIKeyAuthFilter authFilter() {
APIKeyAuthFilter filter = new APIKeyAuthFilter(principalRequestHeader);
filter.setAuthenticationManager(authentication -> {
String principal = (String) authentication.getPrincipal();
if (!principalRequestValue.equals(principal)){
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
});
return filter;
}
I expect to be able to get the header and inspect the value to compare with existing key but, I get this exception "message": "io.undertow.servlet.util.IteratorEnumeration cannot be cast to java.lang.String",
"trace": "java.lang.ClassCastException: io.undertow.servlet.util.IteratorEnumeration cannot be cast to java.lang.String\n\tat uk.co.nesistec.contractpicturechallenge.config.APISecurityConfig.lambda$authFilter$0(APISecurityConfig.java:46)
I got the example code from this other question.
Securing Spring Boot API with API key and secret

I have discovered all I needed was another class to register the security filter.
import org.springframework.security.web.context
.AbstractSecurityWebApplicationInitializer;
public class SpringSecurityInitializer
extends AbstractSecurityWebApplicationInitializer {
//no code needed
}
This is the full example for this scenario for anyone that'll need this. Spring

Related

Roles and Permission at method level Spring boot

I need to have authorization at the method level so that the users with proper permissions only can access it. The method will contain a token as a parameter. I need to make an API call passing the token and get the user email id. Once I have the email id, I need to fetch the user's roles & permissions from the database. Then I invoke the method if the user have appropriate roles else return a 403 error.
Is there a way to get this done in spring boot? I will have multiple methods behind authorization and would like to have some kind of annotation at method level.
Thanks.
#PreAuthorize annotation is what you want
Please read the following link for spring method level authorization
baeldung method authorization
you will also need to undestand SPEL(Spring Expression Language) as this is what the PreAuthorize method gets as parameter , link can be found here
please note that spring uses the SecurityContext to get the user data(Role etc..), meaning that the user already passed the login(authentication) stage and has SecurityContext loaded for said user
Example:
//other annotations
#PreAuthorize("hasRole('ROLE_VIEWER')") // hasRole('ROLE_VIEWER') -> this is SPEL
public ResponseEntity<String> methodName() {
//method
}
You can use #PreAuthorize with more flex as:-
#PreAuthorize("#securityService.hasPermission({'PERMISSION_1'})")
and service:-
#Component("securityService")
public class SecurityService {
public boolean hasPermission(PermissionEnum... permissions) {
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication()
.getAuthorities();
for (PermissionEnum permission : permissions) {
if (authorities.contains(new SimpleGrantedAuthority(permission.toString))) {
return true;
}
}
return false;
}
}
You can make it as you want.
For more
https://dreamix.eu/blog/java/implementing-custom-authorization-function-for-springs-pre-and-post-annotations
https://try2explore.com/questions/10125443

Reactive Spring Security PostAuthorize annotation doesn't work

Using Webflux and Reactive Spring Security, how do you do post processing via annotations to control access to methods?
Trying a very basic sample, I'm not able to get the value from the PostAuthorize annotation. For example
#GetMapping
#PostAuthorize("#email == authentication.principal.email")
public Flux<Project> sampleTest(final String email) {
log.info("email: {}", email);
return Flux.empty();
}
The email will always be null. I have the basic wiring working to the fact if I set something like #PreAuthorize("hasRole('ADMIN')") I'll get back a 403.
I can extract the Authentication out with a helper like:
public Mono<Authentication> getAuthentication() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.flatMap(Mono::just);
}
I may not be understanding your question correctly, but the PostAuthorize uses the return object - the body of the method doesn't have access to anything in the SPEL expression.
Something like this might work -
#GetMapping
#PostAuthorize("returnObject == someCondition")
public Flux<Project> sampleTest(final String email) {
// get some data and return it
}
But maybe you want to filter the items in the Flux?
You might look at the #PostFilter annotation -
// assuming there's an email property on your Project object.
#GetMapping
#PostFilter("filterObject.getEmail() == authentication.principal.email")
public Flux<Project> sampleTest() {
// get some data and return it
}

How to validate request parameters on feign client

Is there a way to add validation to feign clients on the request parameters.
For example:
#FeignClient
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") String zipCode);
}
It would be nice to verify that zipcode is not empty and is of certain length etc, before sending the HTTP call to the server.
If your validations are simple, apply to only headers and query string parameters, you can use a RequestInterceptor for this, as it provides you the opportunity to review the RequestTemplate before it is sent to the Client.
public class ValidatingRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
// use the methods on the request template to check the query and values.
// throw an exception if the request is not valid.
}
}
If you need to validate the request body, you can use a custom Encoder
public class ValidatingEncoder implements Encoder {
public void encode(Object object, Type type, RequestTemplate template) {
// validate the object
// throw an exception if the request is not valid.
}
}
Lastly, if you want to validate individual parameters, you can provide a custom Expander for the parameter and validate it there. You can look at this answer for a complete explanation on how to create a custom expander that can work with Spring Cloud.
How to custom #FeignClient Expander to convert param?
For completeness, I've included an example for how to do this with vanilla Feign.
public class ZipCodeExpander implements Expander {
public String expand(Object value) {
// validate the object
// throw an exception if the request is not valid.
}
}
public interface ZipCodeClient {
#RequestLine("GET /zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#Param(expander = ZipCodeExpander.class) ("zipCode") String zipCode);
}
As pointed out in this comment, a solution using the Bean Validation API would be nice. And indeed, I found in a Spring Boot project that merely placing #org.springframework.validation.annotation.Validated on the interface is sufficient for enabling Bean Validation.
So for example:
#FeignClient
#Validated
public interface ZipCodeClient {
#GetMapping("/zipcodes/{zipCode}")
Optional<ZipCodeView> findByZipCode(#PathVariable("zipCode") #NotEmpty String zipCode);
}
triggering a ConstraintViolationException in the case of violations.
Any standard Bean Validation feature should work here.
UDPATE Note that there seems to be a potential issue with this solution that might require setting a Hibernate Validator configuration property like this: hibernate.validator.allow_parallel_method_parameter_constraint=true

How can I throw mapping to object exception with gson?

I'm trying to force gson to throw an exception when an string does not map to an object which I'm passing to it.
#ResponseStatus(HttpStatus.CREATED)
#PostMapping("offer")
public String postOffer(#RequestBody String jsonBody) {
Offer offer = gson.fromJson(jsonBody, Offer.class);
offerRepository.save(offer);
return offer.getId();
}
Currently, it will just save what ever it can to the db and ignore any elements that don't map to the class. This is bad for me because I get bad data making it's way to the db.
Any help would be appreciated.
ps. using springboot-data-mongodb and gson for mapping.
Thanks
In GSON you cannot make some fields required.
You can handle this i your code, if the variable is not present in json then in Offer object that variable will simple be assigned as null.
You can add null check to your code for the required fields and throw your own exception.
Since gson dont have this facility, you can also try the answer from below link-
Gson optional and required fields
To achieve this you need to follow two steps:-
1) Mark all required field in Offer class as #NotNull(message="your custom message")
2) Add below class to tell Mongo to validate document before persisting it to the database.
#Configuration
public class MongoEventValidationListener {
#Bean
public ValidatingMongoEventListener validatingMongoEventListener() {
return new ValidatingMongoEventListener(validator());
}
#Bean
public LocalValidatorFactoryBean validator() {
return new LocalValidatorFactoryBean();
}
}

How to get custom User object from AuthenticationFailureBadCredentialsEvent / AuthenticationSuccessEvent object

I'm trying to show no of invalid attempts of user using spring security.I'm using a custom User class to get additional user details other than username and password. I've created two listener classes i.e. AuthenticationSuccessEventListener & AuthenticationFailureListener to update user's invalid attempts.
Now in the onApplicationEvent method i'm trying to get custom User object (CustomUserDetails) like shown below:
#Component
public class AuthenticationFailureListener implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {
#Autowired
private ILoginDAO loginDAO ;
#Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
CustomUserDetails user = (CustomUserDetails)event.getAuthentication().getPrincipal();//I get ClassCastException here.
String strUserID = user.getUserID();
CustomUserDetails customUser = loginDAO.loadUserByUsername(strUserID);
if (customUser != null){
...
} } }
event.getAuthentication().getPrincipal() returns a String i.e. username which i'm trying to cast it to CustomUserDetails (custom User class) and i get error.
P.S - I'm entering userid/password in login page and hence i pass userid as parameter for all the methods including loadUserByUsername(strUserID).
How can i get my custom User object in the listener class from AuthenticationFailureBadCredentialsEvent / AuthenticationSuccessEvent object?
The event just contains the authentication request object, i.e. the Authentication which was passed to the AuthenticationManager and which failed. So it will contain the submitted username as the principal.
Authentication may have failed for a variety of reasons, including a non-existent username, and in fact doesn't even need to involve a UserDetails object at all, so if you want the full information you will need to load it using the username.
Alternatively you could customize an AuthenticationProvider to perform the additional work you want in the implementation itself, rather than via an event.

Resources