-2

I'm making a reddit clone for practice and when I run it I'm getting the following exception:

java.lang.ClassCastException: class java.lang.String cannot be cast to class org.springframework.security.core.userdetails.UserDetails (java.lang.String is in module java.base of loader 'bootstrap'; org.springframework.security.core.userdetails.UserDetails is in unnamed module of loader 'app')

Here are the classes:

JwtConfig

@Configuration
@ConfigurationProperties(prefix = "application.jwt")
public class JwtConfig {

    private String secretKey;
    private String tokenPrefix;
    private String tokenExpirationAfterDays;

    public JwtConfig() {
        this.secretKey = System.getenv("SECRET_KEY");
    }

    public String getSecretKey() {
        return secretKey;
    }

    public String getTokenPrefix() {
        return tokenPrefix;
    }

    public void setTokenPrefix(String tokenPrefix) {
        this.tokenPrefix = tokenPrefix;
    }

    public String getTokenExpirationAfterDays() {
        return tokenExpirationAfterDays;
    }

    public void setTokenExpirationAfterDays(String tokenExpirationAfterDays) {
        this.tokenExpirationAfterDays = tokenExpirationAfterDays;
    }
}

JwtUtil

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.LocalDate;
import java.util.stream.Collectors;

public class JwtUtil {

    private final JwtConfig jwtConfig;

    @Autowired
    public JwtUtil(JwtConfig jwtConfig) {
        this.jwtConfig = jwtConfig;
    }

    public String getAccessToken(UserDetails user) {
        Algorithm algorithm = Algorithm.HMAC256(jwtConfig.getSecretKey());
        return JWT.create()
                .withSubject(user.getUsername())
                .withExpiresAt(java.sql.Date.valueOf(LocalDate.now().plusDays(1)))
                .withIssuer("auth0")
                .withClaim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
                .sign(algorithm);
    }

    public String getRefreshToken(UserDetails user) {
        Algorithm algorithm = Algorithm.HMAC256(jwtConfig.getSecretKey());
        return JWT.create()
                .withSubject(user.getUsername())
                .withExpiresAt(java.sql.Date.valueOf(LocalDate.now().plusWeeks(4)))
                .withIssuer("auth0")
                .withClaim("roles", user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
                .sign(algorithm);
    }
}

AuthenticationFilter

import org.springframework.security.core.userdetails.User;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final JwtConfig jwtConfig;

    @Autowired
    public AuthenticationFilter(AuthenticationManager authenticationManager, JwtConfig jwtConfig) {
        this.authenticationManager = authenticationManager;
        this.jwtConfig = jwtConfig;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        return authenticationManager.authenticate(authenticationToken);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        UserMediator userMediator = (UserMediator) authentication.getPrincipal();
        JwtUtil jwtUtil = new JwtUtil(jwtConfig);
        Map<String, String> tokens = new HashMap<>();
        tokens.put("access_token", jwtUtil.getAccessToken(userMediator));
        tokens.put("refresh_token", jwtUtil.getRefreshToken(userMediator));
        response.setContentType(APPLICATION_JSON_VALUE);
        new ObjectMapper().writeValue(response.getOutputStream(), tokens);
    }
}

AuthorizationFilter

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.reddit.jwt.JwtConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.security.sasl.AuthenticationException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class AuthorizationFilter extends OncePerRequestFilter {

    private final JwtConfig jwtConfig;

    @Autowired
    public AuthorizationFilter(JwtConfig jwtConfig) {
        this.jwtConfig = jwtConfig;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (request.getServletPath().equals("/api/login") || request.getServletPath().equals("/api/token/refresh")) {
            filterChain.doFilter(request, response);
        } else {
            String authorizationHeader = request.getHeader(AUTHORIZATION);
            if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
                try {
                    String token = authorizationHeader.substring("Bearer ".length());
                    Algorithm algorithm = Algorithm.HMAC256(jwtConfig.getSecretKey());
                    JWTVerifier verifier = JWT.require(algorithm).build();
                    DecodedJWT decodedJWT = verifier.verify(token);
                    String username = decodedJWT.getSubject();
                    String[] roles = decodedJWT.getClaim("roles").asArray(String.class);
                    Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
                    stream(roles).forEach(role -> {
                        authorities.add(new SimpleGrantedAuthority(role));
                    });
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    filterChain.doFilter(request, response);
                } catch (Exception exception) {
                    response.setStatus(FORBIDDEN.value());
                    Map<String, String> error = new HashMap<>();
                    error.put("error_message", exception.getMessage());
                    response.setContentType(APPLICATION_JSON_VALUE);
                    new ObjectMapper().writeValue(response.getOutputStream(), error);
                }
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }
}

UserDetailServiceImpl

package com.reddit.service;

import com.reddit.entity.User;
import com.reddit.repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@Service
// @Transactional
public class UserDetailServiceImpl implements UserService, UserDetailsService {

    private final UserRepository userRepository;

    @Autowired
    public UserDetailServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<User> userOptional = userRepository.findUserByUsername(username);
        User user = userOptional
                .orElseThrow(() -> new UsernameNotFoundException("No user found with username: " + username));
        return new UserMediator(user);
    }

    private Collection<? extends GrantedAuthority> getAuthorities(String role) {
        return Collections.singletonList(new SimpleGrantedAuthority(role));
    }

    @Override
    @Transactional
    public List<User> getUsers() {
        return (List<User>) userRepository.findAll();
    }

    @Override
    public User getUser(String username) {
        Optional<User> user = userRepository.findUserByUsername(username);
        return user.orElse(null);
    }

    @Override
    public User deleteUser(User user) {
        userRepository.delete(user);
        return user;
    }

    @Transactional
    public boolean isUsernameAlreadyInUse(String username) {
        return userRepository.existsUserByUsername(username);
    }

    @Transactional
    public boolean isEmailAlreadyInUse(String email) {
        return userRepository.existsUserByEmail(email);
    }
}

AuthenticationServiceImpl

import com.reddit.entity.User;

@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    @Autowired
    public AuthenticationServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void saveUser(RegistrationRequest registrationRequest) {
        User user = new User(registrationRequest.getFirstName(),
                registrationRequest.getLastName(),
                registrationRequest.getEmail(),
                registrationRequest.getUsername(),
                passwordEncoder.encode(registrationRequest.getPassword()),
                registrationRequest.getCountry(),
                true);
        userRepository.save(user);
    }
}

UserMediator

import com.reddit.entity.User;
import org.springframework.security.core.userdetails.UserDetails;

public class UserMediator implements UserDetails {

    private User user;

    public UserMediator(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return new ArrayList<>();
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return user.isEnabled();
    }
}

Originally I didn't have UserMediator class, I made it because I saw it in a post with the same problem.

I checked the imports and the classes are imported right.

I have no idea why is this exception happening because Spring is not showing me the stack trace. Any help is welcomed.

Update:

Stack trace: stack trace

Stack trace copied: https://pastebin.com/SQWg9Dpy

odxrs
  • 1
  • 2
  • 1
    My first question is why have you written all this custom handling of JWTs etc when all that code already exists in spring security? I suggest you read the official spring security documentation on how to handle JWTs and look at the official examples instead of getting info from outdated blogs, as spring has had full jwt support since 2018 – Toerktumlare May 12 '22 at 06:55
  • @Toerktumlare I watched Amigoscode's Spring Security and Jwt tutorials on Youtube. That's where the base code is from. Their Jwt videos is from last year so I don't understand why would they code it if it's already in Spring. Anyway, I'll look into docs. Thanks for the directions. – odxrs May 12 '22 at 10:12
  • Because they do like you, watch someone elses tutorials and then make their own. – Toerktumlare May 12 '22 at 11:25
  • 1
    Also, all you have posted is a single line from a full stack trace. And removed the entire ”trace” part. A stacktrace is meant to trace back to the line that actually threw the exception and you have removed it. You are basically saying, i have an error ”somewhere” here is 300 lines of code, please find it. Read up what a ClassCastException is https://stackoverflow.com/questions/907360/explanation-of-classcastexception-in-java and somewhere you are telling the compiler, “here is a string, make it to a user” without a stacktrace we at SO cant be sure what line is faulty – Toerktumlare May 12 '22 at 12:22
  • @Toerktumlare I literally wrote in the post that Spring is not showing me the full stack trace - only one line. I've posted here because I've been stuck with this problem for days, and as I'm a beginner I needed help/guidance from someone experienced. If I wanted someone to serve me a solution I wouldn't start learning coding at all. I know what ClassCastException is. – odxrs May 14 '22 at 17:37
  • i have coded in spring for over 10 years and i have never seen spring logs giving just a single line. Enable debug logs and post the logs here. You have truncated the logs, i dont want your opinion of what is important from the logs, i want to see the entire logs. And if you knew what a ClassCastexception was/is you wouldn't be asking here. But good luck then – Toerktumlare May 15 '22 at 01:44
  • I've updated the post with the screenshot of the error as well as the pastebin of the whole output (couldn't fit it in screenshot). Where did I ask what is ClassCastException? My question is WHY is it happening (because I couldn't figure it out after days of trying). – odxrs May 17 '22 at 14:24
  • I've solved it. It was a stupid beginners mistake lol – odxrs May 18 '22 at 12:04

0 Answers0