0

I've decided to migrate from extending WebSecurityConfigurerAdapter to SecurityFilterChain, and I've met problems with AuthenticationFilter. (With an old method configuration was working).

Everytime, when I'm trying to login by hitting api with postman (/api/users/login), I'm getting 401 HttpStatus in opposite to my expectations (before migration I was getting jwt tokens. (Credentials are correct :d)

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
class SecurityConfiguration {
    private final UserDetailsService userDetailsService;
    private final SuffixConfiguration suffixConfiguration;
    private final AuthorizationService authorizationService;
    private final AuthenticationService authenticationService;
    private String loginURL = "/api/users/login";

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        AuthenticationManager authenticationManager = authenticationManager(http.getSharedObject(AuthenticationConfiguration.class));
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(authenticationManager, authenticationService);
        authenticationFilter.setFilterProcessesUrl(loginURL);
        http.headers().cacheControl();
        http.csrf().disable();
        http.cors();
        http
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, loginURL).permitAll()
                .antMatchers("/api/users/register").permitAll()
                .antMatchers("/api/users/refreshToken").permitAll();
        http
                .addFilter(authenticationFilter)
                .addFilterBefore(new AuthorizationFilter(authorizationService), UsernamePasswordAuthenticationFilter.class);
        http
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http
                .authorizeRequests()
                .antMatchers("/api/department/add-moderator")
                .hasAnyAuthority("[ROLE_ADMIN]");

        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

Here is the code of AuthorizationService

@Slf4j
@Service
public class AuthorizationServiceImpl implements AuthorizationService {
    @Override
    public void tryAuthorize(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader(AUTHORIZATION).substring(TOKEN_PREFIX.length());
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET.getBytes());
            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) {
            log.error("Logging exception: d" + exception.getMessage());
            throw exception;
        }
    }
}

And this is AuthenticationService

@Slf4j
@RequiredArgsConstructor
public class AuthenticationServiceImpl implements AuthenticationService {

    private final TokenService tokenService;
    private final UserModelMapper userModelMapper;

    @Override
    public UsernamePasswordAuthenticationToken createUsernameAuthenticationToken(HttpServletRequest request, HttpServletResponse response) {
        try {
            Map<String, String> requestMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            String username = requestMap.get("username");
            String password = requestMap.get("password");
            log.info(username, password);

            return new UsernamePasswordAuthenticationToken(username, password);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Map<Object, Object> successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
        UserEntity user = (UserEntity) authResult.getPrincipal();
        String issuer = request.getRequestURI();
        String accessToken = tokenService.generateAccessToken(user, issuer);
        String refreshToken = tokenService.generateRefreshToken(user, issuer);
        Map<Object, Object> responseBody = new HashMap<>();
        responseBody.put("access_token", accessToken);
        responseBody.put("refresh_token", refreshToken);
        responseBody.put("user", userModelMapper.mapUserEntityToLoginResponseDTO(user));

        return responseBody;
    }


}

Also as you suggested, code where users are saved

@RequiredArgsConstructor
@Service
@Transactional
class UserManagementServiceImpl implements UserManagementService {

    private final UserRepository userRepository;
    private final SuffixConfiguration suffixConfiguration;
    private final DepartmentFacade departmentFacade;
    private final RoleFacade roleFacade;
    private final UserFindingService userFindingService;
    private final UserModelMapper userModelMapper;

    @Override
    public UserResponseDTO registerNewUser(RegisterNewUserRequestDTO requestDTO) throws IllegalArgumentException {
        checkIfUserWithGivenUsernameAlreadyExists(requestDTO.username());
        UserEntity newUserEntity = createEntityToSave(requestDTO);
        userRepository.save(newUserEntity);

        return userModelMapper.mapUserEntityToUserResponseDTO(newUserEntity);
    }

    @Override
    public void deleteUser(DeleteUserRequestDTO requestDTO) {
        UserEntity requestingUser = userFindingService.getUserEntity(requestDTO.username());
        List<RoleEntity> allowedRoles = Arrays.asList(roleFacade.findByRoleType(RoleType.ROLE_ADMIN), roleFacade.findByRoleType(RoleType.ROLE_MODERATOR));
        if (requestingUser.getRoles().containsAll(allowedRoles)) {
            userRepository.deleteByUsername(requestDTO.username());
        } else {
            throw new UserDoesNotHavePermissionException(requestingUser.getUsername());
        }
    }

    @Override
    public LoginResponseDTO login(LoginRequestDTO requestDTO) {
        UserEntity userEntity = userFindingService.getUserEntity(requestDTO.username());
        isCredentialsCorrect(requestDTO, userEntity);

        return userModelMapper.mapUserEntityToLoginResponseDTO(userEntity);
    }

    private void isCredentialsCorrect(LoginRequestDTO requestDTO, UserEntity userEntity) {
        if (!suffixConfiguration.bCryptPasswordEncoder().matches(requestDTO.password(), userEntity.getPassword())) {
            throw new BadCredentialsException("Bad credentials");
        }
    }

    private UserEntity createEntityToSave(RegisterNewUserRequestDTO requestDTO) throws IllegalArgumentException {
        UserEntity newUserEntity = new UserEntity(requestDTO.username(), encodePassword(requestDTO.password()));
        RoleEntity role = roleFacade.createRoleEntity(requestDTO.role());
        newUserEntity.getRoles().add(role);
        newUserEntity.getDepartmentEntities().add(departmentFacade.getDepartmentEntity(requestDTO.department()));

        return newUserEntity;
    }

    private void checkIfUserWithGivenUsernameAlreadyExists(String username) {
        userRepository.findByUsername(username).ifPresent(user -> {
            throw new UsernameTakenException(username);
        });
    }

    private String encodePassword(String password) {
        if (password != null) {
            return suffixConfiguration.bCryptPasswordEncoder().encode(password);
        } else {
            throw new EmptyPasswordException();
        }
    }
}

Thanks for help

  • 1
    Please share your `AuthenticationFilter` and `AuthorizationFilter`. I thought those were Spring Security's but they aren't. – Marcus Hert da Coregio Aug 18 '22 at 12:29
  • how do you encode password? can you please post code where you save your users with passwords and where some password encoder is used? – Andrei Titov Aug 18 '22 at 12:33
  • take a look for the answer here, might help: https://stackoverflow.com/questions/73382193/spring-boot-spring-security-bcryppasswordencoder-in-new-component-based-secur/73385684 if you inject certain encoder, for example, `BCryptPasswordEncoder`, encode password and store it, and then your filter calls `AuthenticationManager.authenticate` you'll get the 401 because in new spring-security default impl - `ProviderManager` - calls not your encoder bean, but `DelegatingPasswordEncoder`, which expects password as _"{bcrypt}encodedPassword"_ – Andrei Titov Aug 18 '22 at 12:40
  • 1
    updated question with code that you've asked about. @MarcusHertdaCoregio – Mateusz Bednarczyk Aug 18 '22 at 12:45
  • @AndrewThomas appreciate it, but it doesnt work – Mateusz Bednarczyk Aug 18 '22 at 12:49
  • why have you written all this when what you are trying to do is fully supported in spring security, why havn't you read the spring security documentation before asking on stack overflow? also using JWT tokens as sessions is VERY bad and unsecure! – Toerktumlare Aug 18 '22 at 19:19

0 Answers0