How to get user information for logged in user SpringSecurity + AngularJS - spring

Currently I am using the following to log users into my application. However I would like to use an angular function to actually perform the login. To do this I would like to create a rest web service to authenticate, however all the examples I see on SO use User which I thought was Depreciated. I would also like the service to return information about the user.
The short of what I am asking is how can I change the MyUserDetailsService to be used as a restful service for logging in, or how can I create a service that I can use for logging in that will return the user object after logging in.
<form class="navbar-form" action="/j_spring_security_check" method="post">
<input class="span2" name="j_username" type="text" placeholder="Email">
<input class="span2" name="j_password" type="password" placeholder="Password">
<input type="submit" class="btn btn-primary" value="Log in"/>
</form>
This is my authenticationManager
<authentication-manager>
<authentication-provider user-service-ref="MyUserDetailsService">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
</authentication-manager>
Here is the user details service I am currently using for login.
#Service("MyUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(MyUserDetailsService.class);
private UserManager userManager;
#Autowired
public MyUserDetailsService(UserManager userManager) {
this.userManager = userManager;
}
#Override
#Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException, DataAccessException {
if ((email == null) || email.trim().isEmpty()) {
throw new UsernameNotFoundException("Email is null or empty");
}
logger.debug("Checking users with email: " + email);
Users users = userManager.findByEmail(email);
if (users == null) {
String errorMsg = "User with email: " + email + " could not be found";
logger.debug(errorMsg);
throw new UsernameNotFoundException(errorMsg);
}
Collection<GrantedAuthority> grantedAuthorities = toGrantedAuthorities(users.getRoleNames());
String password = users.getPassword();
boolean enabled = users.isEnabled();
boolean userNonExpired = true;
boolean credentialsNonExpired = true;
boolean userNonLocked = true;
return new User(email, password, enabled, userNonExpired, userNonLocked, credentialsNonExpired, grantedAuthorities);
}
public static Collection<GrantedAuthority> toGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> result = newArrayList();
for (String role : roles) {
result.add(new SimpleGrantedAuthority(role));
}
return result;
}
}

There are some JSTL tags for Spring Security you can use on the View to do some things. If JSTL is not an option, for whatever reason(s), you can do something like this:
${pageContext.request.userPrincipal.principal.yourCustomProperty}
Also, you could get the Principal in your Controller and set it on the Model.

Related

Inserting data into related table with thymeleaf

I have table user and table wallet, table wallet have userId so they are connected.
I created controller like this:
#PostMapping("/user/{user_id}/wallets")
public ResponseEntity<?> createWallet(#PathVariable(value = "user_id") Long user_id,
#RequestBody Wallet walletRequest) {
if (walletRepository.existsByUserIdAndWalletName(user_id, walletRequest.getWalletName())) {
return ResponseEntity.badRequest().body(new MessageResponse("You already have wallet with that name, choose another!"));
}
Wallet comment = userRepository.findById(user_id).map(tutorial -> {
walletRequest.setUser(tutorial);
return walletRepository.save(walletRequest);
}).orElseThrow(() -> new IllegalArgumentException("Not found user with id = " + user_id));
return new ResponseEntity<>(comment, HttpStatus.CREATED);
}
And that works fine when I go in postman and hit API /api/user/1/wallets with the appropriate JSON body, I mean wallet is added to user with ID 1.
But I dont know how to transform this to consume in Thymeleaf?
This is all stuff that is related to this thing:
First of all API to show new wallet form:
#GetMapping("/showNewWalletForm")
public String showNewWalletForm(Model model) {
Wallet wallet = new Wallet();
model.addAttribute("wallet", wallet);
return "new_wallet";
}
Form inside html:
<form action="#" th:action="#{/api/wallet/saveWallet}" th:object="${wallet}" method="POST">
<input type="text" th:field="*{walletName}" placeholder="Wallet name" class="form-control mb-4 col-4">
<input type="text" th:field="*{initialBalance}" placeholder="Enter balance" class="form-control mb-4 col-4">
<button type="submit" class="btn btn-info col-2"> Save Wallet</button>
</form>
And API to save wallet:
#PostMapping("/saveWallet")
public String saveWallet(#ModelAttribute("wallet") Wallet wallet) {
// save employee to database
walletService.saveWallet(wallet);
return "redirect:/";
}
Obviously I'm getting Column 'user_id' cannot be null since I didnt set it anywhere as I did in postman.
This is Wallet class:
#Entity
#Table(name = "wallet")
public class Wallet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty(message = "Please, insert a wallet name")
private String walletName;
private double initialBalance;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
To make this work, you need to be able to fetch the logged-in user and since this is something you will need often, I suggest that you create a Spring managed class.
#Component("CurrentUserUtility")
public class CurrentUserUtility {
public static UserDetailsImpl getCurrentUser() {
Authentication tAuthentication = getAuthentication();
if (tAuthentication != null) {
return (UserDetailsImpl) getAuthentication().getPrincipal();
} else {
return null;
}
}
private static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
}
UserDetailsImpl is the class implementing the UserDetails interface and this class should have a userId field (or any other field such as username or email that can be used to uniquely identify the current user).
Now, the only thing you need to do in you controller before saving a new wallet is retrieve the current user using the above utility class.
#PostMapping("/saveWallet")
public String saveWallet(#ModelAttribute("wallet") Wallet wallet) {
//user_id shoudn't be null after the next line
wallet.setUser(userRepository.findById( CurrentUserUtility.getCurrentUser().getUserId() ));
walletService.saveWallet(wallet);
return "redirect:/";
}

Displaying username logged in using thymeleaf fragment header in Spring boot application

I have a fragment for my web application which is used across multiple pages to display a navbar with the user logged in : if they are logged in, if not : display login & signup button.
I want to know how to include my attribute of a logged in user to a fragment as currently I have the logged in user attribute mapped to each controller displaying a web page, not a fragment.
To get the current logged in user I use
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("loggedinuser", authentication.getName());
However this is only specific to one get request. I want to be able to display the logged in user in a fragment which is then called by each page.
Here is an example of a controller getting the logged in user
#GetMapping(path= "")
public String getMainPage(HttpServletRequest request, Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("loggedinuser", authentication.getName());
model.addAttribute("roles", authentication.getAuthorities());
return "mainpage";
}
Here is my class extending WebSecurityConfigurerAdapter to authenticate a users username and password against one in the database
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
String userbyUsernameQuery = "select username, password, '1' as enabled from auth_user where username=?;";
String rolebyUsernameQuery = "SELECT auth_user.username, auth_role.role_name as authority from auth_user\n" +
"INNER JOIN auth_user_role ON auth_user.auth_user_id = auth_user_role.auth_user_id\n" +
"INNER JOIN auth_role ON auth_role.auth_role_id = auth_user_role.auth_role_id\n" +
"WHERE auth_user.username =?";
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery(userbyUsernameQuery)
.authoritiesByUsernameQuery(rolebyUsernameQuery)
.passwordEncoder(passwordEncoder());
}
The part of the navbarHeader fragment I want to change is this
<ul class="nav navbar-nav navbar-right">
<li><a th:href="#{/registration}"><span class="glyphicon glyphicon-user"></span>Sign up</a></li>
<li><a th:href="#{/login}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
</ul>
I want to display the logged in user if logged in & disable the sign up button. If not then display the log in button & signup button
Here is my mainpage page which displays the current user logged in and role which I want to move to the fragment so I do not need to add all the code in each controller & html page.
<div align="right" id="loggedIn" th:if="${loggedinuser != null}">
<div style="margin: 10px"
th:text="'Logged in as: '+${loggedinuser} + ' Role: ' +${roles}">
</div>
</div>
The user class
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User{
private int id;
private String name;
private String lastName;
private String username;
private String password;
private String email;
private String enabled;
public User(String name, Collection<? extends GrantedAuthority> authorities) {
}
}
For this issue I basically utilize ModelAttribute which would be available across all the Presentation compilation. So your controller would like this
public class MyController {
#GetMapping(path= "hi")
public String getMainPage(HttpServletRequest request, Model model) {
return "mainpage";
}
#GetMapping(path= "hello")
public String getMainPage(HttpServletRequest request, Model model) {
return "helloPage";
}
#ModelAttribute("loggedinuser")
public User globalUserObject() {
// Add all null check and authentication check before using. Because this is global
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("loggedinuser", authentication.getName());
model.addAttribute("roles", authentication.getAuthorities());
// Create User pojo class
User user = new User(authentication.getName(), Arrays.asList(authentication.getAuthorities()))
return user;
}
}
Now the object loggedinuser available for all the pages.

How to check if checkbox is checked when submiting a form in Thymeleaf and Spring boot?

I want to check if the checkbox is checked when submitting a form.
I need to validate the user input at server side so I am using Spring MVC Form validator.
I am checking the form with a UserFormValidator class but I do not find how to validate the field checkbox.
The html code:
<form method="post" th:action="#{/addUser}" th:object="${userForm}">
<!-- other fields ... -->
<input type="checkbox" name="isTermsChecked" value="" th:checked="${isChecked}">
<span class="text-danger" th:text="${errorTermsChecked}"></span>
<button type="submit">Get Started</button>
</form>
That's what I did in the Controller class:
#PostMapping(value = "/addUser")
public ModelAndView addUser(#Valid #ModelAttribute("userForm") UserForm userForm, BindingResult bindingResult, String isTermsChecked) {
ModelAndView modelAndView = new ModelAndView();
boolean isChecked = false;
System.out.println("isTermsChecked: "+isTermsChecked);
//check is checkbox checked
if (isTermsChecked == null) {
modelAndView.addObject("isChecked", isChecked);
modelAndView.addObject("errorTermsChecked", "Please accept the Terms of Use.");
}else{
isChecked = true;
modelAndView.addObject("isChecked", isChecked);
modelAndView.addObject("errorTermsChecked", "");
}
if (bindingResult.hasErrors() || isTermsChecked == null) {
modelAndView.setViewName("view_addUser");
} else {
//add user ...
modelAndView.setViewName("view_addUser");
}
return modelAndView;
}
My code seems to work correctly and I do not know if it's the correct way.
I only removed th:field=*{checked} and everything is working properly and that's what I did:
<input name="checked" class="form-check-input" type="checkbox" th:checked="*{checked}" />
and in the controller:
#PostMapping(value = "/contact")
public String contactUsHome(#Valid #ModelAttribute("mailForm") final MailForm mailForm, BindingResult bindingResult)
throws MessagingException {
if (bindingResult.hasErrors()) {
return HOME_VIEW;
} else {
emailService.sendSimpleMail(mailForm);
return REDIRECT_HOME_VIEW;
}
}
and for the validation I used Spring Validation:
public class MailValidator implements Validator {
//...
#Override
public void validate(Object obj, Errors errors) {
//...
MailForm mailForm = (MailForm) obj;
validateChecked(errors, mailForm);
//...
}
private void validateChecked(Errors errors, MailForm mailForm) {
if (mailForm.isChecked() == false) {
errors.rejectValue("checked", "mailForm.checked");
}
}
}

How to bind Spring form:checkbox instead of form:checkboxes?

I am having problems with form:checkbox. I cannot make it display selected values. When I selected values and submit, correct values are display in database. When I load page all values (checkboxes) are not selected.
Elements below are located inside this:
<form:form role="form" commandName="user" class="form-horizontal" action="${form_url}">
</form:form>
This works just fine:
<form:checkboxes items="${availableRoles}" path="roles" itemLabel="role" itemValue="id" element="div class='checkbox'"/>
This doesn't work:
<c:forEach items="${availableRoles}" var="r" varStatus="status">
<div class="checkbox">
<form:checkbox path="roles" label="${r.description}" value="${r.id}"/>
</div>
</c:forEach>
This is my domain class:
public class User {
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
This is my custom property editor:
public class RolePropertyEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) {
Role role = new Role();
role.setId(Integer.valueOf(text));
setValue(role);
}
}
Controller has this method:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Role.class, new RolePropertyEditor());
}
Controller method:
#RequestMapping(value = "/update/{userId}", method = RequestMethod.GET)
public String updateUser(#PathVariable Integer userId, Model model) {
User user = userService.getByUserId(userId);
List<Role> availableRoles = roleService.getAllRoles();
model.addAttribute("availableRoles", availableRoles);
model.addAttribute("user", user);
return "user/update";
}
After debugging session I found the solution.
Because of Spring internals JSP should look like this:
<c:forEach items="${availableRoles}" var="r">
<div class="checkbox">
<form:checkbox path="roles" label="${r.description}" value="${r}" />
</div>
</c:forEach>
Notice that value is item (r), not item's member like r.id.
Also you need getAsText implementation in your custom PropertyEditor.
#Override
public String getAsText() {
Role role = (Role) this.getValue();
return role.getId().toString();
}

Spring MVC Form Validation - The request sent by the client was syntactically incorrect

I am trying to add form validations to a working application. I started by adding a NotNull check to Login Form. I am using Hibernate impl of Bean Validation api.
Here's the code I have written
#Controller
#RequestMapping(value="/login")
#Scope("request")
public class LoginController {
#Autowired
private CommonService commonService;
#Autowired
private SiteUser siteUser;
#InitBinder
private void dateBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy");
CustomDateEditor editor = new CustomDateEditor(dateFormat, true);
binder.registerCustomEditor(Date.class, editor);
}
#ModelAttribute
protected ModelMap setupForm(ModelMap modelMap) {
modelMap.addAttribute("siteUser", siteUser);
return modelMap;
}
#RequestMapping(value="/form", method = RequestMethod.GET)
public ModelAndView form(ModelMap map){
if (siteUser.getId() == null){
map.addAttribute("command",new SiteUser());
return new ModelAndView("login-form",map);
}else {
return new ModelAndView("redirect:/my-dashboard/"+siteUser.getId());
}
}
#RequestMapping(value="/submit", method=RequestMethod.POST)
public ModelAndView submit(#Valid SiteUser user, ModelMap map, BindingResult result){
if (result.hasErrors()) {
map.addAttribute("command", user);
System.out.println("Login Error block");
return new ModelAndView("login/form",map);
}
else {
User loggedInUser = commonService.login(user.getEmail(), user.getPassword());
if (loggedInUser != null) {
siteUser.setId(loggedInUser.getId());
siteUser.setName(loggedInUser.getName());
System.out.println("site user attr set");
}
return new ModelAndView("redirect:/my-dashboard/"+loggedInUser.getId());
}
}
}
The Model is
#Component
#Scope("session")
public class SiteUser {
private Integer id = null;
#NotNull
private String name = null;
private String email = null;
private String password = null;
private List<String> displayPrivList = null;
private List<String> functionPrivList = null;
// And the getters and setters
}
The JSP is
<c:url var="loginSubmitUrl" value="/login/submit"/>
<form:form method="POST" action="${loginSubmitUrl}">
<form:errors path="*" />
<div class="row">
<div class="span4">
</div>
<div class="span4">
<h3>Please Login</h3>
<label><span style="color:red">*</span>Email</Label><form:input path="email" type="text" class="input-medium" />
<label><span style="color:red">*</span>Password</Label><form:input path="password" type="password" class="input-medium" />
<br/>
<button type="submit" class="btn btn-primary">Login</button>
<button type="button" class="btn">Cancel</button>
</div>
</div>
</form:form>
I have added messages.properties and the annotation driven bean def in the context xml.
Other answers on the subject talk about form fields not getting posted. In my case, that's the expected behavior - that if I submit a blank form, I should get an error.
Please advise what am I missing?
I think this question had the same issue as yours
Syntactically incorrect request sent upon submitting form with invalid data in Spring MVC (which uses hibernate Validator)
which just points out
You have to modify the order of your arguments. Put the BindingResult result parameter always directly after the parameter with the #Value annotation
You need this: <form:errors path="email" cssClass="errors" />
Use the tag form:errors for each input with the same "path" name.
It is also possible to list all the error at the same time if you don't put a path.
Here, check an full example with sample code that you can download to learn how to do:
http://www.mkyong.com/spring-mvc/spring-3-mvc-and-jsr303-valid-example/
Can you try changing the <form:form> by including the commandName to it like this
<form:form method="POST" action="${loginSubmitUrl}" commandName="user">

Resources