Oauth-2 authentication extracting the user details expects CustomPrincipal object - spring-boot

I got this code to implement authentication in a Spring boot project. Everything is working fine with the exception of extracting the user who is to be authenticated. I have searched and could not find could not find the solution somewhere else.
public class CustomUserAuthenticationConverter implements UserAuthenticationConverter {
private final String EMAIL = "email";
private Collection<? extends GrantedAuthority> defaultAuthorities;
public void setDefaultAuthorities(String[] defaultAuthorities) {
this.defaultAuthorities = AuthorityUtils
.commaSeparatedStringToAuthorityList(StringUtils.arrayToCommaDelimitedString(defaultAuthorities));
}
#Override
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
response.put(USERNAME, userAuthentication.getName());
if (userAuthentication.getAuthorities() != null && !userAuthentication.getAuthorities().isEmpty())
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(userAuthentication.getAuthorities()));
return response;
}
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME))
return new UsernamePasswordAuthenticationToken(
new CustomPrincipal(map.get(USERNAME).toString(), map.get(EMAIL).toString()), "N/A",
getAuthorities(map));
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
if (!map.containsKey(AUTHORITIES))
return defaultAuthorities;
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String)
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
if (authorities instanceof Collection)
return AuthorityUtils.commaSeparatedStringToAuthorityList(
StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
The other parts are working correctly except for this method snippet
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(USERNAME))
return new UsernamePasswordAuthenticationToken(
new CustomPrincipal(map.get(USERNAME).toString(), map.get(EMAIL).toString()), "N/A",
getAuthorities(map));
return null;
}
I understand that it's expecting a CustomPrincipal object where this Strings are passed as USERNAME, EMAIL. What do I do to pass in the right parameters?

Thanks everyone. The.problem was that the Lombok library to create constructors failed, so the simple answer is to create them manually.
It works perfectly. The CustomPrincipal class needs to have a constructor that accepts 2 arguments which was implicitly defined but not injected when needed in the class instantiation in another class

Related

more than one 'primary' service instance suppliers found during load balancing (spring boot/cloud)

I'm currently updating from Spring boot 2.2.x to 2.6.x + legacy code, it's a big jump so there were multiple changes. I'm now running into a problem with load balancing through an api-gateway. I'll apologize in advance for the wall of code to come. I will put the point of failure at the bottom.
When I send in an API request, I get the following error:
more than one 'primary' bean found among candidates: [zookeeperDiscoveryClientServiceInstanceListSupplier, serviceInstanceListSupplier, retryAwareDiscoveryClientServiceInstanceListSupplier]
it seems that the zookeeperDiscovery and retryAware suppliers are loaded through the default serviceInsatnceListSupplier, which has #Primary over it. I thought would take precedence over the other ones. I assume I must be doing something wrong due changes in the newer version, here are the relevant code in question:
#Configuration
#LoadBalancerClients(defaultConfiguration = ClientConfiguration.class)
public class WebClientConfiguration {
#Bean
#Qualifier("microserviceWebClient")
#ConditionalOnMissingBean(name = "microserviceWebClient")
public WebClient microserviceWebClient(#Qualifier("microserviceWebClientBuilder") WebClient.Builder builder) {
return builder.build();
}
#Bean
#Qualifier("microserviceWebClientBuilder")
#ConditionalOnMissingBean(name = "microserviceWebClientBuilder")
#LoadBalanced
public WebClient.Builder microserviceWebClientBuilder() {
return WebClient.builder();
}
#Bean
#Primary
public ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction(
ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory) {
//the transformer is currently null, there wasn't a transformer before the upgrade
return new CustomExchangeFilterFunction(loadBalancerFactory, transformer);
}
}
There are also some Feign Client related configs here which I will omit, since it's not (or shouldn't be) playing a role in this problem:
public class ClientConfiguration {
/**
* The property key within the feign clients configuration context for the feign client name.
*/
public static final String FEIGN_CLIENT_NAME_PROPERTY = "feign.client.name";
public ClientConfiguration() {
}
//Creates a new BiPredicate for shouldClose. This will be used to determine if HTTP Connections should be automatically closed or not.
#Bean
#ConditionalOnMissingBean
public BiPredicate<Response, Type> shouldClose() {
return (Response response, Type type) -> {
if(type instanceof Class) {
Class<?> currentClass = (Class<?>) type;
return (null == AnnotationUtils.getAnnotation(currentClass, EnableResponseStream.class));
}
return true;
};
}
//Creates a Custom Decoder
#Bean
public Decoder createCustomDecoder(
ObjectFactory<HttpMessageConverters> converters, BiPredicate<Response, Type> shouldClose
) {
return new CustomDecoder(converters, shouldClose);
}
#Bean
#Qualifier("loadBalancerName")
public String loadBalancerName(PropertyResolver propertyResolver) {
String name = propertyResolver.getProperty(FEIGN_CLIENT_NAME_PROPERTY);
if(StringUtils.hasText(name)) {
// we are in a feign context
return name;
}
// we are in a LoadBalancerClientFactory context
name = propertyResolver.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
Assert.notNull(name, "Could not find a load balancer name within the configuration context!");
return name;
}
#Bean
public ReactorServiceInstanceLoadBalancer reactorServiceInstanceLoadBalancer(
BeanFactory beanFactory, #Qualifier("loadBalancerName") String loadBalancerName
) {
return new CustomRoundRobinLoadBalancer(
beanFactory.getBeanProvider(ServiceInstanceListSupplier.class),
loadBalancerName
);
}
#Bean
#Primary
public ServiceInstanceListSupplier serviceInstanceListSupplier(
#Qualifier(
"filter"
) Predicate<ServiceInstance> filter, DiscoveryClient discoveryClient, Environment environment, #Qualifier(
"loadBalancerName"
) String loadBalancerName
) {
// add service name to environment if necessary
if(environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME) == null) {
StandardEnvironment wrapped = new StandardEnvironment();
if(environment instanceof ConfigurableEnvironment) {
((ConfigurableEnvironment) environment).getPropertySources()
.forEach(s -> wrapped.getPropertySources().addLast(s));
}
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(LoadBalancerClientFactory.PROPERTY_NAME, loadBalancerName);
wrapped.getPropertySources().addLast(new MapPropertySource(loadBalancerName, additionalProperties));
environment = wrapped;
}
return new FilteringInstanceListSupplier(filter, discoveryClient, environment);
}
}
There was a change in the ExchangeFilter constructor, but as far as I can tell, it accepts that empty transformer,I don't know if it's supposed to:
public class CustomExchangeFilterFunction extends ReactorLoadBalancerExchangeFilterFunction {
private static final ThreadLocal<ClientRequest> REQUEST_HOLDER = new ThreadLocal<>();
//I think it's wrong but I don't know what to do here
private static List<LoadBalancerClientRequestTransformer> transformersList;
private final Factory<ServiceInstance> loadBalancerFactory;
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory) {
this(loadBalancerFactory);
///according to docs, but I don't know where and if I need to use this
#Bean
public LoadBalancerClientRequestTransformer transformer() {
return new LoadBalancerClientRequestTransformer() {
#Override
public ClientRequest transformRequest(ClientRequest request, ServiceInstance instance) {
return ClientRequest.from(request)
.header(instance.getInstanceId())
.build();
}
};
}
public CustomExchangeFilterFunction (Factory<ServiceInstance> loadBalancerFactory, List<LoadBalancerClientRequestTransformer> transformersList) {
super(loadBalancerFactory, transformersList); //the changed constructor
this.loadBalancerFactory = loadBalancerFactory;;
}
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
// put the current request into the thread context - ugly, but couldn't find a better way to access the request within
// the choose method without reimplementing nearly everything
REQUEST_HOLDER.set(request);
try {
return super.filter(request, next);
} finally {
REQUEST_HOLDER.remove();
}
}
//used to be an override, but the function has changed
//code execution doesn't even get this far yet
protected Mono<Response<ServiceInstance>> choose(String serviceId) {
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerFactory.getInstance(serviceId);
if(loadBalancer == null) {
return Mono.just(new EmptyResponse());
}
ClientRequest request = REQUEST_HOLDER.get();
// this might be null, if the underlying implementation changed and this method is no longer executed in the same
// thread
// as the filter method
Assert.notNull(request, "request must not be null, underlying implementation seems to have changed");
return choose(loadBalancer, filter);
}
protected Mono<Response<ServiceInstance>> choose(
ReactiveLoadBalancer<ServiceInstance> loadBalancer,
Predicate<ServiceInstance> filter
) {
return Mono.from(loadBalancer.choose(new DefaultRequest<>(filter)));
}
}
There were pretty big changes in the CustomExchangeFilterFunction, but the current execution doesn't even get there. It fails here, in .getIfAvailable(...):
public class CustomRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final int DEFAULT_SEED_POSITION = 1000;
private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private final String serviceId;
private final int seedPosition;
private final AtomicInteger position;
private final Map<String, AtomicInteger> positionsForVersions = new HashMap<>();
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId
) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(DEFAULT_SEED_POSITION));
}
public CustomRoundRobinLoadBalancer (
ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId,
int seedPosition
) {
Assert.notNull(serviceInstanceListSupplierProvider, "serviceInstanceListSupplierProvider must not be null");
Assert.notNull(serviceId, "serviceId must not be null");
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.serviceId = serviceId;
this.seedPosition = seedPosition;
this.position = new AtomicInteger(seedPosition);
}
#Override
// we have no choice but to use the raw type Request here, because this method overrides another one with this signature
public Mono<Response<ServiceInstance>> choose(#SuppressWarnings("rawtypes") Request request) {
//fails here!
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get().next().map((List<ServiceInstance> instances) -> getInstanceResponse(instances, request));
}
}
Edit: after some deeper stacktracing, it seems that it does go into the CustomFilterFunction and invokes the constructor with super(loadBalancerFactory, transformer)
I found the problem or a workaround. I was using #LoadBalancerClients because I thought it would just set the same config for all clients that way (even if I technically only have one atm). I changed it to ##LoadBalancerClient and it suddenly worked. I don't quite understand why this made a difference but it did!

Using a Wrapper Type for a DTO in Spring + Jackson

I'm trying to find a more or less elegant way to handle PATCH http operations in Spring MVC.
Basically, I'd like to perform a "dual" Jackson deserialization of a JSON document from a Request Body: one to a Map, and the other to the target POJO. Ideally, I would like to perform this in a single PartialDto<T> instance, where T is my target DTO type.
Better giving an example. Let's say I currently have this PUT mapping in a REST Controller:
#PutMapping("/resource")
public MyDto updateWhole(#RequestBody MyDto dto) {
System.out.println("PUT: updating the whole object to " + dto);
return dto;
}
My idea is to build a PartialDto type that would provide both POJO representation of the request body, as well as the Map representation, like this:
#PatchMapping("/resource")
public MyDto updatePartial(#RequestBody PartialDto<MyDto> partial) {
System.out.println("PATCH: partial update of the object to " + partial);
final MyDto dto = partial.asDto();
// Do stuff using the deserialized POJO
final Map<String, Object> map = partial.asMap();
// Do stuff as a deserialized map...
return dto;
}
I hope this will allow me to further expand the PartialDto implementation so I can perform things like this:
if (partial.hasAttribute("myAttribute")) {
final String myAttribute = dto.getMyAttribute();
// ...
}
Or even using a metamodel generator:
if (partial.hasAttribute(MyDto_.myAttribute)) {
final String myAttribute = dto.getMyAttribute();
// ...
}
So the question is simple: Jackson can easily map a JSON document to a POJO. It can also easily map a JSON document to a java Map. How can I do both at the same time in a Wrapper object such as my PartialDto?
public class PartialDto<T> {
private final Map<String, Object> map;
private final T dto;
PartialDto(Map<String, Object> map, T dto) {
this.map = map;
this.dto = dto;
}
public T asDto() {
return this.dto;
}
public Map<String, Object> asMap() {
return Collections.unmodifiableMap(this.map);
}
}
I tried to use a GenericConverter like this (that, of course, I registered in Spring MVC's FormatterRegistry):
public class PartialDtoConverter implements GenericConverter {
private final ObjectMapper objectMapper;
public PartialDtoConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(String.class, PartialDto.class));
}
#Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final Class<?> targetClazz = targetType.getResolvableType().getGeneric(0).getRawClass();
final Map<String, Object> map;
try {
map = objectMapper.readValue((String) source, Map.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e); // FIXME
}
final Object dto = objectMapper.convertValue(map, targetClazz);
return new PartialDto(map, dto) ;
}
}
And this converter works well when tested directly using Spring's ConversionService:
#SpringBootTest
class ConverterTest {
#Autowired
private ConversionService conversionService;
#Test
public void testPartialUpdate() throws Exception {
final MyDto dto = new MyDto()
.setIt("It");
final PartialDto<MyDto> partialDto = (PartialDto<MyDto>) conversionService.convert(
"{ \"it\": \"Plop\" }",
new TypeDescriptor(ResolvableType.forClass(String.class), null, null),
new TypeDescriptor(ResolvableType.forClassWithGenerics(PartialDto.class, MyDto.class), null, null)
);
Assertions.assertEquals("Plop", partialDto.asDto().getIt());
Assertions.assertEquals("Plop", partialDto.asMap().get("it"));
}
}
However, it doesn't seem to work in a #RequestBody such as shown above. Reminder:
#PatchMapping("/resource")
public MyDto updatePartial(#RequestBody PartialDto<MyDto> partial) {
// ...
}
Any idea is welcome.

How to inject PathVariable id into RequestBody *before* JSR-303 validation is executed?

I'm stuck in an apparently simple problem: I want to perform some custom validation based on the object id in a PUT request.
#RequestMapping(value="/{id}", method=RequestMethod.PUT)
public ResponseEntity<Void> update(#Valid #RequestBody ClientDTO objDto, #PathVariable Integer id) {
Client obj = service.fromDTO(objDto);
service.update(obj);
return ResponseEntity.noContent().build();
}
I'd like to create a custom validator to output a custom message in case I update some field that can't be the same of another object in my database. Something like this:
public class ClientUpdateValidator implements ConstraintValidator<ClientUpdate, ClientDTO> {
#Autowired
private ClientRepository repo;
#Override
public void initialize(ClientInsert ann) {
}
#Override
public boolean isValid(ClientDTO objDto, ConstraintValidatorContext context) {
Client aux = repo.findByName(objDto.getName());
if (aux != null && !aux.getId().equals(objDto.getId())) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Already exists")
.addPropertyNode("name").addConstraintViolation();
return false;
}
return true;
}
}
However, the object id comes from #PathVariable, not from #RequestBody. I can't call "objDto.getId()" like I did above.
On the other hand, it doesn't make much sense to obligate to fill up the object id in the request body, because this way the path variable would become meaninless.
How can I solve this problem? Is there a way to inject the id from PathVariable into RequestBody object before bean validation is executed? If not, what would be a viable solution? Thanks.
Try to inject httpServletRequest into the custom validator
public class ClientUpdateValidator implements ConstraintValidator<ClientUpdate, ClientDTO> {
#Autowired
private HttpServletRequest request;
#Autowired
private ClientRepository repo;
#Override
public void initialize(ClientInsert ann) {
}
#Override
public boolean isValid(ClientDTO objDto, ConstraintValidatorContext context) {
// for example your path to put endpoint is /client/{id}
Map map = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
String id = map.get("id");
Client aux = repo.findByName(objDto.getName());
if (aux != null && !aux.getId().equals(id)) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Already exists")
.addPropertyNode("name").addConstraintViolation();
return false;
}
return true;
}
}

How to use InetOrgPersonContextMapper class

I'm authenticated and authorise to Active Directory by Spring Security.
But can not retrive LDAP attributes, for example MAIL.
I trying use InetOrgPersonContextMapper for it...
#Bean
public InetOrgPersonContextMapper inetOrgPersonContextMapper(){
InetOrgPersonContextMapper contextMapper = new InetOrgPersonContextMapper();
return contextMapper;
}
#Bean
public LdapAuthenticationProvider ldapAuthenticationProvider(){
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapAuthenticator(),ldapAuthoritiesPopulator());
ldapAuthenticationProvider.setUserDetailsContextMapper(inetOrgPersonContextMapper());
return ldapAuthenticationProvider;
}
but when i trying retrive attributes in controller to i get ClassCastExeption
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
InetOrgPerson person = (InetOrgPerson)auth.getPrincipal();
Please tell me correct way for reitrive attributes.
I guess it's no better way, but it's working.
If anybody know how can do it better, please tell me.
#Bean
public UserDetailsContextMapper userDetailsContextMapper(){
return new LdapUserDetailsMapper(){
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
InetOrgPersonContextMapper personContextMapper = new InetOrgPersonContextMapper();
UserDetails cm = personContextMapper.mapUserFromContext(ctx,username,authorities);
String MAIL = ((InetOrgPerson)(personContextMapper.mapUserFromContext(ctx,username,authorities))).getMail();
String FullName = ((InetOrgPerson)(personContextMapper.mapUserFromContext(ctx,username,authorities))).getDisplayName();
System.out.println("MAIL: " + MAIL + " Full Name: " + FullName);
return cm;
}
};
}

java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to UserPrincipal

I made this class UserPrincipal to get the user id of my custom Hibernate User class.
public class UserPrincipal extends org.springframework.security.core.userdetails.User {
public User getUser() {
return user;
}
public UserPrincipal(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities, User user) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.user = user;
}
private final User user;
}
However, when I use it like this:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = ((UserPrincipal) principal).getUser();
I get the following error:
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to UserPrincipal
I think there's an error in the way I implemented the constructor, but I am not sure.
Or does it have to do with a mismatch with the way I log the user in? I am using Spring Security.
instead of extending user class from spring-security you better to implement UserDetails interface given from spring security. please read the below code. Provide custom userDetailsService implementation to your project. i hope this will help you- http://docs.spring.io/spring-security/site/docs/3.0.x/reference/technical-overview.html#d0e1613
public class LoggedUser implements UserDetails{
private User user;
// setter and getter of user
public LoggedUser (User user){
this.user=user;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities=new ArrayList<SimpleGrantedAuthority>();
for (String role : user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
public String getPassword() {
return user.getPassword();
}
public String getUsername() {
// TODO Auto-generated method stub
return user.getUsername();
}
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
now User the below code to get User object:
LoggedUser principal = (LoggedUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = pricipal.getUser();
This is classic case of downcasting. To achieve this, do the following:
Let consider A is the super class and B is sub class:
1. Define this method in the subclass(B):
public static B method(A a) {
B b = null;
if (a instanceof A) {
b = (B) a;// downcasting
}
return b;
}
2. Now in your working class(e.g., Service, Controller etc), do the following
A b = new B(); // Initialize super class object with subclass
BeanUtils.copyProperties(a,b);//where a being the instance of A(user in your case), with values
B c = B.method(b);
just check the user ur logged in with ur security user DB.

Resources