How to test secured Endpoints/Rest Controller? Java, with #Secured - java

I have a REST Controller, which is secured and needs to be tested:
#RestController
#RequestMapping("/account")
public class ViewerEditorEndpoint {
private final ViewerEditorService viewerEditorService;
public ViewerEditorEndpoint(ViewerEditorService viewerEditorService) {
this.viewerEditorService = viewerEditorService;
}
#PutMapping("/{username}")
#Secured({"ROLE_VOLUNTEER", "ROLE_INDIVIDUAL", "ROLE_COMPANY"})
UserDTO editAccountInfo(#PathVariable String username, #RequestBody UserDTO userDTO) {
Optional<UserDTO> oUser= viewerEditorService.editAccountInfo(username, userDTO);
return oUser.orElse(null);
}
#GetMapping("/{username}")
//#Secured({"ROLE_VOLUNTEER", "ROLE_INDIVIDUAL", "ROLE_COMPANY"})
UserDTO getAccountInfo(#PathVariable String username) {
if (username.equals(principal.getName())) {
return viewerEditorService.getAccountInfo(username).orElse(null);
}
return null;
}
}
I tried it with #WithMockUser, #WithUserDetails, but none really works. I saw a lot of examples with Mockmvc but I don't really understand how to implement this. I would appreciate some tipps(:

Related

How to get data from another service using Feign Client Spring Boot (ERROR: 406)

When I call another Service (User-service) from one service (API-gateway) Using Feign Client, I'm getting an Error
There are two services
User-service
API-gateway
In my API-gateway
FeignClient
#FeignClient(contextId = "user-by-email",name = "user-service")
#Service
public interface UserByEmail {
#RequestMapping(value = "/email/{email}", consumes= MediaType.APPLICATION_JSON_VALUE)
User findByEmail(#PathVariable("email") String email);
}
Controller
#RequestMapping("/test")
public class TestController {
#Autowired
private UserByEmail userByEmail;
#GetMapping(value = "/{email:.+}")
public ResponseEntity testUser(#PathVariable("email") String username) {
return ResponseEntity.ok(userByEmail.findByEmail(username));
}
}
I need to call the following (User-service)
Controller
#EnableFeignClients
#RestController
public class UserController extends BaseController<User> {
#Autowired
private UserService userService;
#PostConstruct
public void binder() {
init(this.userService);
}
#GetMapping(value = "/email/{email}")
public ResponseEntity findByEmail(#PathVariable("email") String email) {
return ResponseEntity.ok(userService.findByEmail(email));
}
}
Repository
#Override
public User findByEmail(String email) {
Query query = new Query(Criteria.where("email").is(email).and("status").is(1));
return mongoOperations.findOne(query, User.class);
}
Service
#Override
public User findByEmail(String email) {
return userDao.findByEmail(email);
}
The Error I'm getting is ..
<Map>
<timestamp>1583924335777</timestamp>
<status>406</status>
<error>Not Acceptable</error>
<message>Could not find acceptable representation</message>
<trace>org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:246)
Can anyone please explain What is wrong with my code, And give your valuable solutions
(Basically I need to create Security in API-gateway, in order to control the access of other services)
Try Implementing Feign client as below:
#FeignClient(name = "user-service")
public interface UserByEmail {
#RequestMapping(method = RequestMethod.GET, value = "/email/{email}", consumes = "application/json")
User findByEmail(#PathVariable("email") String email);
}
also make sure the Fields passed over JSON matchers User POJO.
I've got the solution to my Question
FeignClient
#FeignClient(contextId = "user-by-email",name = "user-service")
#Service
public interface UserByEmail {
#RequestMapping(value = "/email/{email}", consumes= MediaType.APPLICATION_JSON_VALUE)
User findByEmail(#RequestParam("email") String email);
}
Controller
#RequestMapping("/test")
public class TestController {
#Autowired
private UserByEmail userByEmail;
#GetMapping(value = "/{email}")
public ResponseEntity testUser(#RequestParam("email") String username) {
return ResponseEntity.ok(userByEmail.findByEmail(username));
}
}
In User-service
Controller
#EnableFeignClients
#RestController
public class UserController extends BaseController<User> {
#Autowired
private UserService userService;
#PostConstruct
public void binder() {
init(this.userService);
}
#GetMapping(value = "/email/{email}")
public ResponseEntity findByEmail(#RequestParam("email") String email) {
return ResponseEntity.ok(userService.findByEmail(email));
}
}

Spring Boot registration form testing - NullPointerException

I learning Spring Boot web application with .jsp, and I'm struggling a lot with the testing concepts. From the SO and YT guides I implemented the Mockito thing, but honestly I do not clearly undesrtand how does it work.
I have a Registration form with 4 fields for name, lastname, email and password. This POST request is handled by the registerAction method in RegisterController. In this method I have two self-written validators for email and password. The tests should handle the cases when User data are given properly and if the errors are sent when inputs are not correct.
I tried to write tests for the controller but I'm constantly getting an exception NullPointerExpection. Looking into the debugger, the User object sent from the testing class has null attributes, which probably is the reason the exceptions.
Testing class:
#SpringBootTest
#AutoConfigureMockMvc
class RegisterControllerTest {
#Autowired
private WebApplicationContext wac;
#MockBean
private UserService userService;
#Autowired
private Gson gson;
#Autowired
private MockMvc mockMvc;
#BeforeEach
void setUp() {
initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.apply(springSecurity()).build();
}
#Test
void show_register_action() throws Exception {
User user = prepareUserEmpty();
this.mockMvc.perform(post("/adduser")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(user)))
.andDo(print())
.andExpect(status().isOk());
}
private User prepareUserEmpty(){
User user = new User();
user.setEmail("");
user.setPassword("");
user.setName("");
user.setLastName("");
return user;
}
}
RegisterController:
#Controller public class RegisterController {
#Autowired
private UserService userService;
#Autowired
private EmailSender emailSender;
#Autowired
MessageSource messageSource; // Allows to obtain messages from message.properties to Java code
#POST
#RequestMapping(value = "/adduser")
public String registerAction(User user, BindingResult result, Model model, Locale locale){ // BindingResult for validation, Locale for messageSource
String returnPage = "register";
User userExist = userService.findUserByEmail(user.getEmail());
new UserRegisterValidator().validate(user, result);
new UserRegisterValidator().validateEmailExist(userExist, result);
if (!(result.hasErrors())){
userService.saveUser(user);
model.addAttribute("message", messageSource.getMessage("user.register.success.email", null, locale));
returnPage = "index";
}
return returnPage;
} }
Validators:
public class UserRegisterValidator implements Validator {
#Override
public boolean supports(Class <?> cls){
return User.class.equals(cls);
}
#Override
public void validate(Object obj, Errors errors){
User u = (User) obj;
ValidationUtils.rejectIfEmpty(errors, "name", "error.userName.empty");
ValidationUtils.rejectIfEmpty(errors, "lastName", "error.userLastName.empty");
ValidationUtils.rejectIfEmpty(errors, "email", "error.userEmail.empty");
ValidationUtils.rejectIfEmpty(errors, "password", "error.userPassword.empty");
if (!u.getEmail().equals(null)){
boolean isMatch = AppdemoUtils.checkEmailOrPassword(AppdemoConstants.EMAIL_PATTERN, u.getEmail());
if (!isMatch)
errors.rejectValue("email", "error.userEmailIsNotMatch");
}
if (!u.getPassword().equals(null)){
boolean isMatch = AppdemoUtils.checkEmailOrPassword(AppdemoConstants.PASSWORD_PATTERN, u.getPassword());
if (!isMatch)
errors.rejectValue("password", "error.userPasswordIsNotMatch");
}
}
public void validateEmailExist(User user, Errors errors){
if (user != null)
errors.rejectValue("email", "error.userEmailExist");
}
}

Validation in service layer (SpringBoot)

I have a DTO which is validated at Controller layer with a mix of BeanValidation (javax.validation) and a custom Validator (org.springframework.validation.Validator). This way I can check if the input provided is valid and then convert the DTO in an entity and forward it to the Service layer.
#Data
public class UserDTO {
#NotBlank
#Size(max = 25)
private String name;
#NotNull
private Date birthday;
#NotNull
private Date startDate;
private Date endDate;
private Long count;
}
public class UserDTOValidator implements Validator {
private static final String START_DATE= "startDate";
private static final String END_DATE= "endDate";
private static final String COUNT= "count";
#Override
public boolean supports(Class<?> clazz) {
return UserDTO.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserDTO vm = (UserDTO) target;
if (vm.getEndDate() != null) {
if (vm.getStartDate().after(vm.getEndDate())) {
errors.rejectValue(START_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
if (vm.getEndDate().equals(vm.getStartDate()) || vm.getEndDate().before(vm.getStartDate())) {
errors.rejectValue(END_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
}
if (vm.getCount() < 1) {
errors.rejectValue(COUNT, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
.....
}
}
public class UserController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserDTOValidator());
}
#PostMapping()
public ResponseEntity<UserDTO> create(#RequestBody #Valid UserDTO userDTO) {
.....
}
.....
}
Then there is the business logic validation. For example: the #Entity User's startDate must be after some event occurred and the count has to be greater than some X if the last created User's birthDay is in Summer, in other case, the entity should be discarded by the User service.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private SomeEventService someEventService ;
#Override
public User create(User entity) {
String error = this.validateUser(entity);
if (StringUtils.isNotBlank(error)) {
throw new ValidationException(error);
}
return this.userRepository.save(entity);
}
....
private String validateUser(User entity) {
SomeEvent someEvent = this.someEventService.get(entity.getName());
if (entity.getStartDate().before(someEvent.getDate())) {
return "startDate";
}
User lastUser = this.userRepository.findLast();
....
}
}
However I feel like this is not the best approach to handle business logic validation. What should I do? ConstraintValidator/HibernateValidator/JPA Event listeners? Can they work at #Entity class level or I have to create X of them for each different field check? How do you guys do it in a real production application?
In my suggestion,
Use classic field level validation by #Valid
sample
void myservicemethod(#Valid UserDTO user)
For custom business level validation in entity level, create validate method in DTO itself
sample
class UserDTO {
//fields and getter setter
void validate() throws ValidationException {
//your entity level business logic
}
}
This strategy will help to keep entity specific validation logic will be kept within the entity
If still you have validation logic that requires some other service call, then create custom validation annotation with custom ConstraintValidator (eg. question on stackoverflow). In this case, my preference will be to invoke UserDTO.validate() from this custom validator in spiote of calling from service
This will help to keep your validation logic separated from service layer and also portable and modular

How to handle user information after login succeed in Spring Security

I want to use user-information after login succeed. I thought about storing it into session attribute. or using #scope('session) annotation. but I haven't found the best way of doing it. so I just stored it into model-attribute.
#Controller
#RequestMapping(value = "/user")
public class UserController {
#Autowired
private UserService userService;
#Autowired
private UserProfileService userProfileService;
#ModelAttribute("user")
public User getUserModel () {
return userService.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName());
}
#ModelAttribute("userProfile")
public UserProfile getUserProfile() {
return userProfileService.findByUser(userService.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName()));
}
#GetMapping("/")
public String userIndex() {
logger.info("UserIndex");
return "userPage";
}
As you can see, SecurityContextHolder.getContext().getAuthentication().getName() --> this method repeated. every time user make HTTP request, is this good practice? or any better way of store user-infomation in application?
I would go with this.
#RequestMapping(value = "/username", method = RequestMethod.GET)
#ResponseBody
public String currentUserName(Authentication authentication) {
return authentication.getName();
}

Jax-RS Filter pass object to resource

I want to pass the user object I use for authentication in a filter to the resource. Is it possible?
I'm using wildfly 10 (resteasy 3)
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Inject
private UserDao userDao;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
logger.warn("Filter");
String uid = requestContext.getHeaderString("Authorization");
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private User validateUser(String uid) {
return userDao.getById(uid);
}
}
There are two ways I could see to do this. The first is, perhaps, the more standard way but is also more code. Ultimately you'll inject the user as part of the request. However, the first thing you need for this solution is a Principal. A very simple one might be:
import java.security.Principal;
...
public class UserPrinicipal implements Prinicipal {
// most of your existing User class but needs to override getName()
}
Then, in your filter:
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return user;
}
#Override
public boolean isUserInRole(String role) {
// whatever works here for your environment
}
#Override
public boolean isSecure() {
return containerRequestContext.getUriInfo().getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
// again, whatever works
}
});
In the class where you want the User, you could do something like:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething() {
User user = (User)securityContext.getUserPrincipal();
}
}
I've implemented it this way and it works pretty well. However, an arguably simpler way is to just store the user in the session:
#Context
private HttpServletRequest request;
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
request.getSession().setAttribute("user", user);
Then, in your service:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething(#Context HttpServletRequest request) {
User user = (User)request.getSession().getAttribute("user");
}
}
The downside of the second method is that you are really no longer a stateless service as you're storing state somewhere. But the HttpSession is there even if you don't use it.

Categories

Resources