5

I am creating a X-Auth-Token from a user. But, when I try to retrieve the user from the generated token then it gives me an exception

Warning:   StandardWrapperValve[com.security.util.AppConfig]: Servlet.service() for servlet com.security.util.AppConfig threw exception
java.lang.IllegalStateException: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.security.core.userdetails.UserDetails, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.ByteArrayInputStream@7e618679; line: 1, column: 1]
    at com.security.util.TokenHandler.fromJSON(TokenHandler.java:81)
    at com.security.util.TokenHandler.parseUserFromToken(TokenHandler.java:55)
    at com.security.util.TokenAuthenticationService.getAuthentication(TokenAuthenticationService.java:43)
    at com.security.util.StatelessAuthenticationFilter.doFilter(StatelessAuthenticationFilter.java:34)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:316)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:734)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673)
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:416)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:283)
    at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:459)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:167)
    at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:206)
    at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:180)
    at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:235)
    at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:283)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:200)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:132)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:111)
    at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:536)
    at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:56)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:137)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:591)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:571)
    at java.lang.Thread.run(Thread.java:745)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of org.springframework.security.core.userdetails.UserDetails, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.ByteArrayInputStream@7e618679; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.instantiationException(DeserializationContext.java:857)
    at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:139)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3562)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2648)
    at com.security.util.TokenHandler.fromJSON(TokenHandler.java:79)
    ... 42 more

For token generation I am using:

public String createTokenForUser(UserDetails user) {
        byte[] userBytes = toJSON(user);
        byte[] hash = createHmac(userBytes);
        final StringBuilder sb = new StringBuilder(170);
        sb.append(toBase64(userBytes));
        sb.append(SEPARATOR);
        sb.append(toBase64(hash));
        return sb.toString();
    }

And in order to retrieve a user from token I am using:

public UserDetails parseUserFromToken(String token) {
        final String[] parts = token.split(SEPARATOR_SPLITTER);
        if (parts.length == 2 && parts[0].length() > 0 && parts[1].length() > 0) {
            try {
                final byte[] userBytes = fromBase64(parts[0]);
                final byte[] hash = fromBase64(parts[1]);

                boolean validHash = Arrays.equals(createHmac(userBytes), hash);
                if (validHash) {
                   //NEXT LINE I GET AN EXCEPTION
                    final UserDetails user = fromJSON(userBytes);
                        return user;
                }
            } catch (IllegalArgumentException e) {
                //log tempering attempt here
            }
        }
        return null;
    }

These are the other utility methods that was consumed in the above code:

  private UserDetails fromJSON(final byte[] userBytes) {
            try {
                return new ObjectMapper().readValue(new ByteArrayInputStream(userBytes), UserDetails.class);
            } catch (IOException e) {
                throw new IllegalStateException(e);
            }
        }

        private byte[] toJSON(UserDetails user) {
            try {
                return new ObjectMapper().writeValueAsBytes(user);
            } catch (JsonProcessingException e) {
                throw new IllegalStateException(e);
            }
        }

        private String toBase64(byte[] content) {
            return DatatypeConverter.printBase64Binary(content);
        }

        private byte[] fromBase64(String content) {
            return DatatypeConverter.parseBase64Binary(content);
        }

        // synchronized to guard internal hmac object
        private synchronized byte[] createHmac(byte[] content) {
            return hmac.doFinal(content);
        }


EDIT # 1:

public class CustomUserDetails extends org.springframework.security.core.userdetails.User{

    private User user;

    public CustomUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public CustomUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }


    public CustomUserDetails(User user, Collection<? extends GrantedAuthority> authorities) {
        super(user.getUsername(), user.getPassword(), authorities);
        this.user = user;
    }

    public CustomUserDetails(User user, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        super(user.getUsername(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
        this.user = user;
    }

    public User getUser() {
        return user;
    }
}

And modified my toJSON and fromJSON Method

private UserDetails fromJSON(final byte[] userBytes) {
        try {
            return new ObjectMapper().readValue(new ByteArrayInputStream(userBytes), CustomUserDetails.class);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    private byte[] toJSON(UserDetails user) {
        try {

            return new ObjectMapper().writeValueAsBytes( new CustomUserDetails(
                                user.getUsername(),
                                     user.getPassword(),  user.getAuthorities()));
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }

Now the exception is a changed to : 
Caused by: com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.security.CustomUserDetails]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)

2 Answers 2

13

You can write your own deserializer. Put JsonDeserialize annotation on related field.

    public class LoginUser implements UserDetails {

    ....getters and setters...

        @JsonDeserialize(using = CustomAuthorityDeserializer.class)
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return this.mAuthorities;
        }
}

Create deserializer for that field.

public class CustomAuthorityDeserializer extends JsonDeserializer {

    @Override
    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        ObjectMapper mapper = (ObjectMapper) jp.getCodec();
        JsonNode jsonNode = mapper.readTree(jp);
        List<GrantedAuthority> grantedAuthorities = new LinkedList<>();

        Iterator<JsonNode> elements = jsonNode.elements();
        while (elements.hasNext()) {
            JsonNode next = elements.next();
            JsonNode authority = next.get("authority");
            grantedAuthorities.add(new SimpleGrantedAuthority(authority.asText()));
        }
        return grantedAuthorities;
    }

}
Sign up to request clarification or add additional context in comments.

Comments

0

UserDetails is an interface, this is the reason of your error the ObjectMapper read() method is trying to instantiate it and failing. Provide a custom implementation of UserDetails as an argument of ObjectMapper.read() method and it should work.

3 Comments

Thanks for your prompt reply, I have added an implementation for UserDetails and also modified my two methods. But, now getting another exception
You should implement UserDetails not User and provide it to ObjectMapper.readValue(). Can you update the question with the new error?
I have implemented user details. I have already updated the exception in the last line. No suitable constructor found for type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.