How to port SpringMVC Application to SpringREST? - spring-mvc

We have created SpringMVC application using Spring-Boot and Thymleaf. Now as per new requirement, I have to convert them to SPring-REST for external application consumption(AngularJS and Android App) without affecting the thymleaf pages.
Please assist.
Below is the sample code. Like this many controllers are there
#Controller
#RequestMapping(value = "/admin/register")
#SessionAttributes("roles")
public class AdminRegisterController {
#Autowired
private UserService userService;
#Autowired
private RoleRepository roleRepository;
#ModelAttribute("user")
public User constructUser() {
return new User();
}
#ModelAttribute("roles")
public List<Role> InitializeRoles() {
return roleRepository.findAll();
}
// Display Register Page
#RequestMapping
public String showRegister(Model model) {
model.addAttribute("current", "register");
return "register";
}
// Inserting new User
#RequestMapping(method = RequestMethod.POST)
public ModelAndView doRegister(#Valid #ModelAttribute("user") User user, BindingResult result) {
if (result.hasErrors()) {
return new ModelAndView("register");
}
userService.save(user);
RedirectView redirectView = new RedirectView("/admin/register?success=true");
redirectView.setExposeModelAttributes(false);
return new ModelAndView(redirectView);
}
#RequestMapping("/available")
#ResponseBody
public String available(#RequestParam String username) {
User user = userService.findOne(username);
Boolean available = userService.findOne(username) == null;
return available.toString();
}
}

You can use the javax.ws.rs api for doing that.
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0</version>
</dependency>
And use #RestController instead of simple #Controllers in your current code.

Related

UserManager.GetUserAsync without tracking changes?

I'm using the following function to check whether the person that wants to access some database record is an owner of this record:
public class AccessGuard
{
public async Task<bool> IsOwnerOrHaveRightsAsync(UserManager<ApplicationUser> userManager, ApplicationUser claimant, ClaimsPrincipal User)
{
ApplicationUser fullUser = await userManager.GetUserAsync(User);
if (claimant.Id == fullUser.Id)
{
return true;
}
return false;
}
}
It works, but as I've noticed: ApplicationUser is now added to ChangeTracker. What it means is I cannot call userManager.GetUserAsync later in code, because I get this error:
The instance of entity type 'ApplicationUser' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I usually use .AsNoTracking() while accessing database records, but there is nothing like that in userManager. How would you solve this?
I am using it in MVC Controller method as follows:
if (!await new AccessGuard().IsOwnerOrHaveRightsAsync(_userManager, Post.Author, User))
{
return Unauthorized();
}
You cannot use .AsNoTracking() with await userManager.GetUserAsync(User);. Alternatively you can do as follows:
public class AccessGuard
{
private readonly ApplicationDbContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;
public AccessGuard(ApplicationDbContext context, IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_context = context;
}
public async Task<bool> IsOwnerOrHaveRightsAsync(UserManager<ApplicationUser> userManager, ApplicationUser claimant, ClaimsPrincipal User)
{
var loggedInUserId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
ApplicationUser fullUser = _context.ApplicationUsers.AsNoTracking()
.FirstOrDefaultAsync(au => au.Id == loggedInUserId);
if (claimant.Id == fullUser.Id)
{
return true;
}
return false;
}
}
Then you should register IHttpContextAccessor in the Startup class as follows:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Or you can also register as follows
services.AddHttpContextAccessor();
}
Then to access AccessGuard service in your MVC controller method, first register AccessGuard in Startup as follows:
services.AddScoped<AccessGuard>();
Then in your controller method:
public IActionResult Index()
{
AccessGuard accessGuardService = (AccessGuard) HttpContext.RequestServices.GetService(typeof(AccessGuard));
// Now call `accessGuardService` service method here
return View();
}
You can also get AccessGuard service as follows:
AccessGuard accessGuardService = HttpContext.RequestServices.GetService<AccessGuard>();
and it requires namespace using Microsoft.Extensions.DependencyInjection;

Sprint Rest AuthenticationPrincipal returns customUser with only null values

I am trying to get the currently logged in user (basic auth) in a spring boot REST backend:
#RequestMapping(value = "/someURL", method = RequestMethod.GET)
public #ResponseBody Map someMethod(
Authentication auth, // <==== works
#AuthenticationPrincipal(expression = "liquidoUserModel") UserModel liquidoUserModel
)
{
log.debug(auth.getPrincipal()) // <==== THIS WORKS
log.debug(liquidoUserModel) // <==== returns an intance with empty fields
}
Here is my custom UserModel
#Data // Lombok magic for all the getters and setters
#Entity
#RequiredArgsConstructor
#Table(name = "users")
public class UserModel {
#Id
Long id;
#NonNull
#Column(unique = true)
public String email;
[...]
}
And this is my UserDetailsService
public class LiquidoUserDetailsService implements UserDetailsService {
#Autowired
UserRepo userRepo;
#Override
public LiquidoAuthUser loadUserByUsername(String email) throws UsernameNotFoundException {
UserModel userModel = userRepo.findByEmail(email);
return new LiquidoAuthUser(userModel.getEmail(), userModel.getPasswordHash(), getGrantedAuthorities(userModel), userModel);
}
}
And finally the LiquidoAuthUser
public class LiquidoAuthUser extends User {
private UserModel liquidoUserModel;
public LiquidoAuthUser(String username, String password, Collection<? extends GrantedAuthority> authorities, UserModel liquidoUserModel) {
super(username, password, authorities);
this.liquidoUserModel = liquidoUserModel;
}
public UserModel getLiquidoUserModel() {
return liquidoUserModel;
}
public void setLiquidoUserModel(UserModel userModel) {
this.liquidoUserModel = userModel;
}
}
And of course I have the #EnableWebMvc annotation on my main SpringApplication class.
My problem: How can I get the currently authenticated custom UserModel in the REST handler?
The strange thing: I actually can get my custom user from the Authentication auth object. Why does the #AuthenticationPrincipal not work?
I am using spring-security-4.1.3-RELEASE
Full code is open source at https://github.com/Doogiemuc/liquido-backend-spring
I tried and debug your code but not able to find issue of
#AuthenticationPrincipal. Typically this annotation is resolved by AuthenticationPrincipalArgumentResolver class of spring security web annotation. By using #EnableWebSecurity you will automatically have this added to your Spring MVC configuration. Need to be debug more on AuthenticationPrincipalArgumentResolver that for time being I will suggest go with Authentication class and get your object.

Spring MVC - How to create a proper Service layer?

I'm using SpringBoot and I am trying to create a service layer for my web application but i cant make it work.
My classes look like this
ServiceFactory
#Service
public class ServiceFactory {
#Autowired
public static EncuestaService getEncuestaService()
{
return new EncuestaServiceImpl();
}
}
EncuestaService
public interface EncuestaService {
void crearEncuesta(Encuesta encuesta, Map<String,String> parametros);
}
EncuestaServiceImpl
#Service
public class EncuestaServiceImpl implements EncuestaService {
#Override
public void crearEncuesta(Encuesta encuesta, Map<String, String> parametros) {
CrearEncuesta nueva = new CrearEncuesta(encuesta,parametros);
nueva.execute();
}
}
CrearEncuesta
#Service
public class CrearEncuesta {
private Encuesta encuesta;
private Map<String,String> parametros;
#Autowired
private RespuestasRepository respuestasRepository;
#Autowired
private EncuestasRepository encuestasRepository;
public CrearEncuesta(Encuesta encuesta, Map<String,String> parametros) {
super();
this.encuesta = encuesta;
this.parametros = parametros;
}
public void execute()
{
encuestasRepository.save(encuesta);
}
}
Everytime I call ServiceFactory.getEncuestasService().crearEncuesta() from any Controller it returns me a NullPointerException.
From what I have been reading I should not be creating a new EncuestsaServiceImpl() in my ServiceFactory but I don't really know the correct way to do so. I would appreciate if anyone could help me out :P.
Edit:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Controller
#Controller
public class EncuestaController {
#RequestMapping(value ="registrarEncuesta", method = RequestMethod.POST)
private String formularioEncuesta(#Valid #ModelAttribute("formEncuesta") EncuestaForm formEncuesta, BindingResult bindingResult,#RequestParam Map<String,String> allRequestParams)
{
if (bindingResult.hasErrors()) {
return "nuevaEncuesta";
}
try {
Encuesta nueva = formEncuesta.toEncuesta();
ServiceFactory.getEncuestaService().crearEncuesta(nueva,allRequestParams);
} catch (DataIntegrityViolationException e) {
return "nuevaEncuesta";
}
return "redirect:/encuestas";
}
}
You will have to read a little bit more about dependency injection. The central principle in Spring Framework is dependency injection which should be used to avoid referencing beans (service implementations, repository implementations etc...) statically. Spring container also servers as a bean factory that will instantiate and inject (autowire) implementations to beans that need them.
Because Spring will instantiate service interface implementations for you, you don't need ServiceFactory. In your controller you need to add a reference (a field) to EncuestaService and annotate it as Autowired and Spring will wire in the implementation. And then you can just use it in your controller.
#Controller
public class EncuestaController {
#Autowired
EncuestaService encuestaService;
#RequestMapping(value ="registrarEncuesta", method = RequestMethod.POST)
private String formularioEncuesta(#Valid #ModelAttribute("formEncuesta") EncuestaForm formEncuesta, BindingResult bindingResult,#RequestParam Map<String,String> allRequestParams)
{
if (bindingResult.hasErrors()) {
return "nuevaEncuesta";
}
try {
Encuesta nueva = formEncuesta.toEncuesta();
encuestaService.crearEncuesta(nueva,allRequestParams);
} catch (DataIntegrityViolationException e) {
return "nuevaEncuesta";
}
return "redirect:/encuestas";
}
}

Inheritance with #Controller in Spring

I would like have a #Controller parent in my app, as this:
#Controller
public class controllerParent{
public String getUser (Model model,HttpServletRequest request){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); //get logged in username
user = userService.findByUserName(name);
model.addAttribute("user",user);
}
}
and a child:
public class chil extends controllerParent{
#RequestMapping(value="/something.html",method = RequestMethod.GET)
public String doSomething (Model model,HttpServletRequest request){
//Code to do something
}
}
And what want to do is that in each child controller call I want show in my JSP the user . Can I do this without rewrite spring security code?

Spring MVC - PropertyEditor not called during ModelAttribute type conversion

Using Spring 3.2.3, I'm trying to implement a simple CRUD controller that handles REST-ful URLs. It relies on a PropertyEditor to convert a path variable to a BusinessService entity by loading it from an application service. Code is as follows:
#Controller
public class BusinessServiceController {
#Autowired
private BusinessServiceService businessSvcService;
public BusinessServiceController() {
}
#InitBinder
public void initBinder(final WebDataBinder binder) {
binder.registerCustomEditor(BusinessService.class, new BusinessServicePropertyEditor(businessSvcService));
}
#RequestMapping(value = "/ui/account/business-services/{businessSvc}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView update(#ModelAttribute("businessSvc") #Valid final BusinessService businessSvc, final BindingResult result,
final RedirectAttributes redirectAttribs) throws UnknownBusinessServiceException {
ModelAndView mav;
if (result.hasErrors()) {
mav = new ModelAndView("/business-service/edit");
}
else {
businessSvcService.updateBusinessService(XSecurity.principal().getId(), businessSvc);
mav = new ModelAndView("redirect:/ui/account/business-services");
redirectAttribs.addFlashAttribute("message", Message.info("businessService.updated", businessSvc.getTitle()));
}
return mav;
}
}
public class BusinessServicePropertyEditor extends PropertyEditorSupport {
private final BusinessServiceService businessSvcService;
public BusinessServicePropertyEditor(final BusinessServiceService businessSvcService) {
this.businessSvcService = businessSvcService;
}
#Override
public String getAsText() {
final BusinessService svc = (BusinessService) getValue();
return Long.toString(svc.getId());
}
#Override
public void setAsText(final String text) {
final BusinessService svc = businessSvcService.getBusinessService(Long.parseLong(text));
setValue(svc);
}
}
According to SPR-7608, starting from Spring 3.2, #ModelAttribute method argument resolution checks if a path variable by the same name exists (it does here), in which case it tries to convert that path variable's value to the target parameter type through registered Converters and PropertyEditors. This is not what I'm experiencing. When I inspect what ServletModelAttributeMethodProcessor does, it clearly uses the request DataBinder's ConversionService to perform type conversion, which does not consider registered PropertyEditors, and hence BusinessServicePropertyEditor#setAsText is never called.
Is this a configuration problem or an actual bug?
Thanks for your help!
Spring's ConversionService and Converters are replacement for standard Java Beans PropertyEditors.
You need to implement Converter instead of PropertyEditor if this feature is based purely on conversion service.
To register your custom converters in WebDataBinder you might use ConfigurableWebBindingInitializer or #InitBinder method.

Resources