What's the best approach to avoid repeating the same userService DB lookup over and over again in my controller methods?
I'm using Spring Boot 1.5.2 with spring-boot-starter-security and spring-boot-starter-thymeleaf for templating.
I tried adding an instance variable for SecurityContextHolder.getContext().getAuthentication() but it gave me a NullPointerException.
#Controller
public class DashboardController {
#Autowired
private UserService userService;
#Value("${product.name}")
private String productName;
#RequestMapping(value="/dashboard", method = RequestMethod.GET)
public ModelAndView home() {
ModelAndView modelAndView = new ModelAndView();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findUserByEmail(auth.getName());
modelAndView.addObject("email", user.getEmail());
modelAndView.setViewName("dashboard");
return modelAndView;
}
#RequestMapping(value="/dashboard/faq", method = RequestMethod.GET)
public ModelAndView faq(){
ModelAndView modelAndView = new ModelAndView();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findUserByEmail(auth.getName());
modelAndView.addObject("email", user.getEmail());
modelAndView.addObject("productname", productName);
modelAndView.setViewName("faq");
return modelAndView;
}
If you want to get at the user that is stored in the session, you can use this annotation:
#RequestMapping("/me")
public User me(#AuthenticationPrincipal User user) {
return user;
}
If you then want the user to always be available in thymeleaf I would use a #ControllerAdvice
#ControllerAdvice(annotations = Controller.class)
public class GlobalVariablesControllerAdvice {
#ModelAttribute("user")
public User user() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = null;
// get user from authentication, but make sure to check for nulls
return user;
}
}
Related
I can't log in to my app as a user with the role admin or as a user with the role user. I always log in as a user with the role anonymous.
Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder encoder() {
return new StandardPasswordEncoder("53cr3t");
}
#Autowired
UserDetailsServiceImpl userDetailsService;
public SecurityConfig(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/").authenticated()
.antMatchers("/rentAppPage/").hasRole("ADMIN")
.antMatchers("/addVehicle").hasRole("ADMIN")
.antMatchers("/getVehicle").hasRole("ADMIN")
.antMatchers("/removeVehicle").hasRole("ADMIN")
.antMatchers("/updateVehicle").hasRole("ADMIN")
.antMatchers("/allUser").hasRole("ADMIN")
.antMatchers("/resultGet").hasRole("ADMIN")
.antMatchers("/addUser").hasRole("ADMIN")
.antMatchers("/getUser").hasRole("ADMIN")
.antMatchers("/updateUser").hasRole("ADMIN")
.antMatchers("/removeUserById").hasRole("ADMIN")
.antMatchers("/price").hasAnyAuthority("ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allScooter").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allCar").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allMotorBike").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/allBike").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
.antMatchers("/distance").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
.antMatchers("/user").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/rent").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/rent2").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/buy").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/buy2").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/thanks").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.antMatchers("/rentAppPage").hasAnyAuthority( "ROLE_ADMIN", "ROLE_USER")
.and()
.formLogin()
.defaultSuccessUrl("/", true)
.and()
.logout()
.logoutSuccessUrl("/");
;
http.sessionManagement()
//.expiredUrl("/sessionExpired.html")
.invalidSessionUrl("/login.html");
}
}
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idUser;
private String username;
private String password;
private String name;
private String surname;
private String email;
private double latitude;
private double longitude;
private String role;
private String locationName;
}
#Slf4j
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
IUserDAO userDAO;
public UserDetailsServiceImpl(IUserDAO userDAO){
this.userDAO = userDAO;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDAO.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("User dont exist");
}
return new MyUserDetails(user);
}
}
#Repository
public class UserDAOImpl implements IUserDAO {
#Autowired
SessionFactory sessionFactory;
public UserDAOImpl(SessionFactory sessionFactory){
this.sessionFactory = sessionFactory;
}
#Override
public void addUser(User user){
Session session = null;
Transaction tx = null;
try{
session = this.sessionFactory.openSession();
tx = session.beginTransaction();
session.save(user);
tx.commit();
}catch (HibernateException e){
if(tx != null)tx.rollback();
}finally {
session.close();
}
}
#Override
public User findByUsername(String username) {
Session session = this.sessionFactory.openSession();
User user =(User) session.createQuery("FROM pl.edu.wszib.model.User WHERE username = :username" )
.setParameter("username", username)
.uniqueResult();
session.close();
return user;
}
}
public class MyUserDetails implements UserDetails {
private User user;
public MyUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(user.getRole());
return Arrays.asList(authority);
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
2021-01-18 17:05:43.545 DEBUG 4256 --- [io-8080-exec-10] o.s.s.w.a.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#cd98cfcc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#0: RemoteIpAddress: 127.0.0.1; SessionId: AD82C9600EFB66CF7C6F8A1BCCEEAE0D; Granted Authorities: ROLE_ANONYMOUS'
i have databse in MySQL with two users first role admin second role user my role in database is ROLE_ADMIN,ROLE_USER i my full code is here https://github.com/Conrado1212/SpringSecurityWhyCantWorkFine
can someone explain why i cant log in to app as user with role admin or user ?
Of course, there are many questions about encoder, md5, etc. But it's your choice even it's wrong )))
I run your code, if MD5 hash is correct in DB I can login. As I understand, the question is why the application does not store the state of logged in user?
Expectation:
1st request: call method (incognito) -> login -> call method (authorized)
Xth request: call method -> Spring Security checks -> call method (authorized)
Reality:
Xth request: always equals 1st
The cause that you don't change a state of Spring Security context. You call /login endpoint, check if the User is in DB, check his password and return success response. You should create an authorization and place it in the Spring Security context.
Simple example:
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.generateToken(authentication);
return token;
And in next requests use the token. How to implement the token provider there are many examples. It can be custom or better JWT.
If you don't want tokens (RESTful), you can use sessions in requests (RESTless) but it requires additional configuration.
Looked through the code you have posted on github and none of the code you posted in the above question is the problem. You problem is how you perform your login.
login.html
<form action="/authenticate" method="post" id="login" class="input-group">
<div th:text="`enter code here`${errorMessage}" id="error-message"></div>
<input type="text" class="input-field" placeholder="Enter username" name="username" th:field="*{userModel.username}" required>
<input type="password" class="input-field" placeholder="Enter password" name="password" th:field="*{userModel.password}"required>
<input type="checkbox" class="chech-box"><span class="span1">Remember password</span>
<button type="submit" class="submit-btn">Log in</button>
</form>
Here we se that the login form posts to the endpoint /authenticate. This is NOT the standard /login endpoint that spring security sets up for you automatically.
Since you are not using the the standard i find your custom endpoint.
LoginController.java
#RequestMapping(value = "/authenticate",method = RequestMethod.POST)
public String authenticateUser(#ModelAttribute("userModel")User user,Model model){
boolean authResult = this.authenticationService.authenticationUser(user);
if(authResult){
System.out.println("zalogowano !!");
return "rentAppPage";
} else {
model.addAttribute("errorMessage","zle dane!!!");
model.addAttribute("userModel",new User());
return "login";
}
}
Here we see that you pass the user object into a custom written function called authenticationUser. So if we look in that function we find this implementation.
AuthenticationService.java
#Override
public boolean authenticationUser(User user){
User userFromDb = this.userDAO.findByUsername(user.getUsername());
return userFromDb != null && userFromDb.getPassword().equals(DigestUtils.md5Hex(user.getPassword()));
}
All you do in this function, is to fetch the user from the database. Check if the users password matches, and then return a boolean.
If the boolean is true you return the next page.
This is NOT how spring security works. All of the above code is completely faulty, and its clear that no research has been done before asking here on stack overflow. How could the server know you have called that function before? it can't.
My answer to your question, is that your implementation is completely wrong, and i highly suggest you find a getting started guide to how form login works in spring security, because explaining how spring security works, can not be done in a simple answer.
All im going to do is to link you to the official Spring security FormLogin documentation, and you should start reading there.
I was using this tutorial : spring boot CRUD application with tymeleaf
The problem happen when I try to had a user, I got this error :
2020-03-15 09:51:09.413 ERROR 10168 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at com.CRUD.CRUD_EXAMPLE.controller.UserController.addUser(UserController.java:33) ~[classes/:na]
Here's the code :
'''
#Controller
public class UserController {
private UserRepository userRepository;
#GetMapping("/signup")
public String showSignUpForm(User user) {
return "add-user";
}
#PostMapping("/adduser")
public String addUser(#Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-user";
}
userRepository.save(user);
model.addAttribute("users", userRepository.findAll());
return "index";
}
#GetMapping("/edit/{id}")
public String showUpdateForm(#PathVariable("id") long id, Model model) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
model.addAttribute("user", user);
return "update-user";
}
#PostMapping("/update/{id}")
public String updateUser(#PathVariable("id") long id, #Valid User user,
BindingResult result, Model model) {
if (result.hasErrors()) {
user.setId(id);
return "update-user";
}
userRepository.save(user);
model.addAttribute("users", userRepository.findAll());
return "index";
}
#GetMapping("/delete/{id}")
public String deleteUser(#PathVariable("id") long id, Model model) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
userRepository.delete(user);
model.addAttribute("users", userRepository.findAll());
return "index";
}
}
'''
My line 33 is 'userRepository.save(user)';
Please mark your UserRepository implementation class with the #Repository annotation, like this:
#Repository
public class UserRepositoryImpl implements UserRepository {
Then inject the dependency via the #Autowired annotation in your controller like this:
#Controller
public class UserController {
private UserRepository userRepository;
#Autowired
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
This will fix your NullPointerException, because Spring will now create and inject this dependency for you.
As it says you, The object User user that comes to the method is null, or one of its field is null.
Make sure that this objects contains all data coming from view.
Check also if all the field in your html file are well mapped to the field of the User object.
I have this annotation at the top of my controller:
#SessionAttributes("user")
And this mapping:
#RequestMapping(value="/logout", method = RequestMethod.GET)
public String logout(ModelMap model){
model.clear();
But when I navigate to that URL it's still able to retrieve the User session attributes..
How do I properly clear the ModelMap value?
Looks like I need this signature instead w/ SessionStatus:
#RequestMapping(value="/logout", method = RequestMethod.GET)
public String logout(SessionStatus status){
status.setComplete();
return "redirect:/";
}
#RestController
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value={"/logins"}, method = RequestMethod.GET)
public ModelAndView logins(#RequestBody User user, BindingResult bindingResult){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("logins");
return modelAndView;
}
#RequestMapping(value= {"/login"}, method=RequestMethod.POST)
public ModelAndView login(#Valid #RequestBody User user, BindingResult bindingResult){
ModelAndView modelAndView = new ModelAndView();
User userExists = userService.findUserByEmail(user.getEmail());
if (userExists != null) {
bindingResult
.rejectValue("email", "error.user",
"There is already a user registered with the email provided");
}
if (bindingResult.hasErrors()) {
modelAndView.setViewName("login");
} else {
userService.saveUser(user);
modelAndView.addObject("successMessage", "User has been login successfully");
modelAndView.addObject("user", new User());
modelAndView.setViewName("login");
}
return modelAndView;
}
#RequestMapping(value="/admin/home", method = RequestMethod.GET)
public ModelAndView home(){
ModelAndView modelAndView = new ModelAndView();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findUserByEmail(auth.getName());
modelAndView.addObject("userName", "Welcome " + user.getName());
modelAndView.addObject("adminMessage","Content Available Only for Users with Admin Role");
modelAndView.setViewName("admin/home");
return modelAndView;
}
}
#Service("userService")
public class Userserviceimpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
public User findUserByEmail(String email) {
return userRepository.findByEmail(email);
}
#Override
public void saveUser(User user) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setActive(1);
Role userRole = roleRepository.findByRole("SUPERADMIN");
user.setRoles(new HashSet<Role>(Arrays.asList(userRole)));
userRepository.save(user);
}
}
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
}
public class Role {
#Id
private int roll_id;
private String role;
...
}
My input in postman
[{
"email":"abc#gmail.com",
"password":"pass",
"name":"JOHN",
"active":"1",
"roles":[{
"role":"admin"
}]
}]
if i send it I have an error like this
{
"timestamp": 1508938909379,
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"path": "/login"
}
I have 3 tables like user and role and user_role in mysql database.
Your json body is in array. Try
{ "email":"abc#gmail.com",
"password":"pass",
"name":"JOHN",
"active":"1",
"roles":[{ "role":"admin" }] //check this in your User.class if it is list,Ok. if not remove box brackets
}
I would like to know how to correctly store and retrieve my user in the session via Spring Security. I am using an AuthenticationManager. So far I am using the following approach which I know isn't the cleanest
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value="/myAccount", method=RequestMethod.GET)
public void myAccount(HttpServletRequest request){
//Verify Logged in user
User user = null;
UserProfile loggedinUser = (UserProfile) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(loggedinUser != null){
user = userService.loadUserByUsername(loggedinUser.getUsername());
}
//Store User in session
Session session = request.getSession(true);
session.setAttribute("user", user);
}
}
I took a look at the answer here: Set User Object in session using Spring
But I don't fully understand how I should implement it. Could I possibly implement my User class like this
public class User implements UserDetails {
private String username;
private String password;
private String email;
......
}
And then modify my controller like this
public class UserController {
#Autowired
private UserDetailsService userDetailsService;
#RequestMapping(value="/myAccount", method=RequestMethod.GET)
public void myAccount(){
User user = (User)SecurityContextHolder.
getContext().getAuthentication().getPrincipal();
}
}
From my understanding, Spring will automatically load my custom User from the database and store it in the session?
Thanks