#Component
public class JwtAuthenticationFilter implements GatewayFilter {
#Autowired
private JwtUtil jwtUtil;
#Override
public Mono<Void> filter(final ServerWebExchange exchange,
final GatewayFilterChain chain) {
ServerHttpRequest request = (ServerHttpRequest) exchange.getRequest();
if (!request.getHeaders().containsKey("Authorization")) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
final String token = request.getHeaders().getOrEmpty("Authorization").get(0);
try {
jwtUtil.validateToken(token);
} catch (JwtTokenMalformedException | JwtTokenMissingException e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.BAD_REQUEST);
return response.setComplete();
}
Claims claims = jwtUtil.getClaims(token);
exchange.getRequest().mutate().header("id", String.valueOf(claims.get("id"))).build();
return chain.filter(exchange);
}
}
How to add JUnit test cases for this method. I tried, but my test cases were failing
Related
I have implemented all the necessary configuration for generating response when user get Unauthorized but I'm getting empty response with 401 status code.
Configuration class
class WebSecurityConfig extends WebSecurityConfigurerAdapte {
#Autowired
private UserService myUserDetailsService;
// #Autowired
// private JwtAuthenticationEntryPoint entryPoint;
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtRequestFilter jwtRequestFilter(){
return new JwtRequestFilter();
}
#SuppressWarnings("deprecation")
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint() {
return new JwtAuthenticationEntryPoint();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/users/authenticate").permitAll()
.antMatchers("/users/add").permitAll()
.anyRequest().authenticated().and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint());
httpSecurity.addFilterBefore(jwtRequestFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
jwtAuthenticationEntryPoint class
#Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(401, "Unauthorized");
response.setStatus(SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
ExceptionResponse res = new ExceptionResponse(HttpStatus.UNAUTHORIZED, ExceptionMessage.USER_UNAUTHORIZED);
logger.error("User unauthorized", authException);
byte[] body = new ObjectMapper().writeValueAsBytes(res);
response.getOutputStream().write(body);
}
Exceptionhandler.class
#RestControllerAdvice
public class CustomizedResponseEntityExceptionHandler {
#ExceptionHandler(UserUnauthorizedException.class)
public ResponseEntity<ExceptionResponse> handleBadRequestExceptions(UserUnauthorizedException unauthorizedException) {
ExceptionResponse response = new ExceptionResponse(unauthorizedException.getHttpStatus(), unauthorizedException.getMessage());
return new ResponseEntity<>(response, HttpStatus.UNAUTHORIZED);
}
}
UserUnauthroized class
#Getter
public class UserUnauthorizedException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = -7144521128771062072L;
private HttpStatus httpStatus;
public UserUnauthorizedException(String message) {
super(message);
this.httpStatus = HttpStatus.UNAUTHORIZED;
}
public UserUnauthorizedException(String message, HttpStatus httpStatus) {
super(message);
this.httpStatus = httpStatus;
}
}
ExceptionResponse class
#Getter
public class ExceptionResponse {
// #JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "Asia/Kolkata")
// #JsonFormat(timezone = "Asia/Kolkata")
private Date timestamp;
private Integer httpStatusCode;
private HttpStatus httpStatus;
private String message;
private String debugMessage;
private ExceptionResponse() {
timestamp = new Date();
}
public ExceptionResponse(HttpStatus status, String message) {
this();
this.httpStatusCode = status.value();
this.httpStatus = status;
this.message = message;
}
public ExceptionResponse(HttpStatus status, String message, Throwable ex) {
this();
this.httpStatusCode = status.value();
this.httpStatus = status;
this.message = message;
this.debugMessage = ex.getLocalizedMessage();
}
}
OutPut I got while test in postman
enter image description here
while we test the services in postman we are getting empty response in the body!!
We are expecting response body when user get unautorized.
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
I use spring boot with thymeleaf, spring security and spring cloud gateway.
User enter login/password and get a token. I search a way to get this tokin and put it in a cookie or in a hidden field in fragment. Need to to do some ajax call from thymeleaf page.
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebFluxSecurityConfig {
#Autowired
private WebFluxAuthManager authManager;
#Autowired
private WebFluxSecurityContextRepository webFluxSecurityContextRepository;
#Bean
protected SecurityWebFilterChain securityFilterChange(ServerHttpSecurity http) throws Exception {
http.csrf().disable()
.securityContextRepository(webFluxSecurityContextRepository)
.authorizeExchange()
// URL that starts with / or /login/
.pathMatchers("/", "/login", "/js/**", "/img/**", "/css/**").permitAll()
.anyExchange().authenticated()
.and().formLogin().loginPage("/login")
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/two")
).and().csrf().disable();
http.authenticationManager(authManager);
return http.build();
}
}
#Component
public class WebFluxSecurityContextRepository implements ServerSecurityContextRepository {
private final WebFluxAuthManager authManager;
public WebFluxSecurityContextRepository(WebFluxAuthManager authManager) {
this.authManager = authManager;
}
#Override
public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
return Mono.empty();
}
#Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String authToken = authHeader.substring(7);
Authentication auth = new UsernamePasswordAuthenticationToken(authToken, authToken);
return this.authManager.authenticate(auth).map((authentication) -> {
return new SecurityContextImpl(authentication);
});
} else {
return Mono.empty();
}
}
}
#Component
public class WebFluxAuthManager implements ReactiveAuthenticationManager {
#Value("${gateway.url}")
private String gatewayUrl;
#Autowired
private WebClient webClient;
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
Mono<ResponseEntity<String>> mResponse = webClient.post()
.uri("/auth/login")
.acceptCharset(Charset.forName("UTF-8"))
.body(Mono.just(loginRequest), LoginDto.class)
.retrieve()
.toEntity(String.class);
...
...
return Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
}
I was trying to implement basic authentication and authorization in springboot. But i am getting 403 Forbidden error when i send a get request to an endpoint.I have already added JWT token in Authorization header. I am trying to send a Get request to "/user".
Here's the code which I have written
UserController
#RequestMapping(value = "/user")
#RestController
public class UserController {
#Autowired
UserService userService;
#PostMapping
public UserDetailsResponseModel createUser(#RequestBody UserDetailsRequestModel userRequestObject)
{
UserDetailsResponseModel userResponse = new UserDetailsResponseModel();
UserDTO userDto = new UserDTO();
BeanUtils.copyProperties(userRequestObject,userDto);
UserDTO createdUser = userService.createUser(userDto);
BeanUtils.copyProperties(createdUser,userResponse);
return userResponse;
}
#GetMapping
public String getUser()
{
return "Get user was called";
}
}
WebSecurity Class
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserService userService,BCryptPasswordEncoder bCryptPasswordEncoder)
{
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, SecurityConstants.SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()));
}
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
}
Authorization Filter
public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager)
{
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader(SecurityConstants.HEADER_STRING);
if(header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX));
{
chain.doFilter(request,response);
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
chain.doFilter(request,response);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(SecurityConstants.HEADER_STRING);
token = token.replace(SecurityConstants.TOKEN_PREFIX,"");
String user = Jwts.parser()
.setSigningKey(SecurityConstants.TOKEN_SECRET)
.parseClaimsJws(token)
.getBody()
.getSubject();
if(user != null){
return new UsernamePasswordAuthenticationToken(user,null,new ArrayList<>());
}
return null;
}
}
Authentication Filter
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager)
{
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserLoginRequestModel creds = new ObjectMapper().readValue(request.getInputStream(),UserLoginRequestModel.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getEmail(),
creds.getPassword(),
new ArrayList<>()
)
);
}
catch (IOException ex){
throw new RuntimeException(ex);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
String userName = ((User) authResult.getPrincipal()).getUsername();
String token = Jwts.builder()
.setSubject(userName)
.setExpiration(new Date(System.currentTimeMillis()+SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512,SecurityConstants.TOKEN_SECRET)
.compact();
UserService userService = (UserService) SpringApplicationContext.getBean("userServiceImplementation");
UserDTO userDTO = userService.getUser(userName);
response.addHeader(SecurityConstants.HEADER_STRING,SecurityConstants.TOKEN_PREFIX+token);
response.addHeader("UserID",userDTO.getUserId());
}
}
Just started using Jwt tokens when securing my microservices and keep getting a null token in my JwtTokenFilter class when a request is sent in but don't know where from, and finding it hard to understand why?
JwtTokenFilter.class
public class JwtTokenFilter extends OncePerRequestFilter {
private JwtTokenProvider jwtTokenProvider;
public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
this.jwtTokenProvider = jwtTokenProvider;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtTokenProvider.resolveToken(request);
System.out.println("Token: " + token);
try {
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication auth = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (CustomException ex) {
SecurityContextHolder.clearContext();
response.sendError(ex.getHttpStatus().value(), ex.getMessage());
return;
}
filterChain.doFilter(request, response);
}
SecurityConfig.class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenProvider jwtTokenProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/auth/login").permitAll()
.antMatchers("/auth/register").permitAll()
.antMatchers("/auth/{username}").permitAll()
.anyRequest()
.authenticated();
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
}
JwtTokenProvider.class
#Component
public class JwtTokenProvider {
#Value("$security.jwt.token.secret-key")
private String secretKey;
private long validityInMilliseconds = 3600000;
#Autowired
private CustomUserDetails customUserDetails;
#PostConstruct
protected void init() {
secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
}
public String createToken(String username, List<Role> roles) {
Claims claims = Jwts.claims().setSubject(username);
claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).collect(Collectors.toList()));
Date now = new Date();
Date validity = new Date(now.getTime() + validityInMilliseconds);
return Jwts.builder()//
.setClaims(claims)//
.setIssuedAt(now)//
.setExpiration(validity)//
.signWith(SignatureAlgorithm.HS256, secretKey)//
.compact();
}
public Authentication getAuthentication(String token) {
UserDetails userDetails = customUserDetails.loadUserByUsername(getUsername(token));
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
public String getUsername(String token) {
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
}
public String resolveToken(HttpServletRequest req) {
String bearerToken = req.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
throw new CustomException("Expired or invalid JWT token", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
If you need any more classes shown just ask, thanks for the help.
Realised the problem was how I registered it with my Gateway microservice in the spring configuration. All sorted thanks for the help.