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