How to get a principal with #AuthenticationPrincipal [duplicate] - spring-security

This question already has answers here:
Spring boot: Get current user's username
(6 answers)
Closed 6 months ago.
I make a project on Spring Security.
The teacher wants me to get a principal with #AuthenticationPrincipal
I have this controller,
what should I remake to insert #AuthenticationPrincipal?
#Controller
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#GetMapping("/user")
public String showUser(Model model) {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
model.addAttribute("user", user);
return "users";
}
}
My git: https://github.com/anatoliy19/3.1.2.git

#GetMapping("/user")
public String showUser(Model model, #AuthenticationPrincipal User user) {
model.addAttribute("user", user);
return "users";
}
Line with User delete.

Related

Spring Security #PreAuthorize not seeing Role on incoming Request

As stated in the title, I am currently in the process of creating a REST API, using Spring Security to create a Role System for handling the protection of Endpoints. For this I use the #PreAuthorize("hasRole('ROLE_SOMEROLEHERE')") method, however it does not seem to recognize the roles from any request I have tried.
Below I will share some of the related code, hopefully someone can help me understand what is going wrong. If more code is required to see what might be wrong, I'll make sure to add it. Much appreciation in advance!
License Controller
#PostMapping("/create-license")
public void createAccount(#RequestBody License licenseToCreate)
{
licenseRepository.save(licenseToCreate);
}
UserDetailsServiceImpl
public class UserDetailsServiceImpl implements UserDetailsService {
private IUserRepository userRepository;
public UserDetailsServiceImpl(IUserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ApplicationUser foundApplicationUser = userRepository.findByUsername(username);
if (foundApplicationUser == null) {
throw new UsernameNotFoundException(username);
}
return new User(foundApplicationUser.getUsername(), foundApplicationUser.getPassword(), getAuthority(foundApplicationUser));
}
private Set getAuthority(ApplicationUser user) {
Set authorities = new HashSet<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRoles().getRoleName()));
return authorities;
}
}
Did you try to replace #PreAuthorize("hasRole('ROLE_SOMEROLEHERE')") with #PreAuthorize("hasAnyAuthority('ROLE_SOMEROLEHERE')")?
Because in UserDetailsServiceImpl it looks like you're assigning authorities to the user.

PermissionEvaluator targetDomainObject is always null when checking hasPermission

I am developing a Spring Data Rest project, and want to check for user permission when he creates or updates a resource
Here's my laptop class
#Getter
#Setter
#Entity
public class Laptop {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String model;
#Column
private Long userId;
#PrePersist
void onCreate() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
AppUser appUser = (AppUser) authentication.getPrincipal();
userId = appUser.getId();
}
}
Now what I want to do, is to check before user updates the laptop, to make sure that he's updating his laptop and not other's.
Here's my LaptopRepository
#RepositoryRestResource(collectionResourceRel = "content")
public interface LaptopRepository extends CrudRepository<Laptop, Long> {
#Override
//#PreAuthorize("hasPermission(#entity, 'CREATE')")
#PreAuthorize("#entity.userId == principal.id")
<S extends Laptop> S save(S entity);
}
And here's the PermissionEvaluator's method
#Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
return true;
}
Now the point is
How do I distinguish between POST and PUT methods?
If I change my #PreAuthorize to just pass the entity to the hasPermission method to just test, it always gets passes as null
As stated in the guides
import org.springframework.security.access.method.P;
...
#PreAuthorize("#c.name == authentication.name")
public void doSomething(#P("c") Contact contact);
import org.springframework.data.repository.query.Param;
...
#PreAuthorize("#n == authentication.name")
Contact findContactByName(#Param("n") String name);
Adding any of the annotations resolved the problem with null object, and I can distinguish between PUT and POST checking if target's id is null or not

Is it possible to load granted authorities and other user information AFTER authentication succeeds when using a UserDetailsService?

I have been able to get a Spring Security based application up and running, and it has been satisfying all my requirements until now.
I do have 1 doubt regarding how UserDetailsService is used in Spring Security. I have a custom 'UserDetailsService' implementation, which goes like this -
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserDetailsDto> userDetailsByEmail = // Load userDetailsDto from database
if (!userDetailsByEmail.isPresent()) {
throw new UsernameNotFoundException("Username does not exists");
}
UserDetailsDto userDetailsDto = userDetailsByEmail.get();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
return new LoggedInUser(userDetailsDto, roles, modulePermissions, userType);
}
}
The class LoggedInUser is an extension of Spring Security's org.springframework.security.core.userdetails.User class, which goes like this -
public class LoggedInUser extends User {
private static final long serialVersionUID = -1L;
private Long userId;
private boolean firstLogin;
private UserType userType;
private List<ModulePermission> modulePermissions;
private String firstName;
private String lastName;
private String contactNo;
public LoggedInUser(UserDetailsDto userDetailsDto, List<Role> roles, List<ModulePermission> modulePermissions,
UserType userType) {
super(userDetailsDto.getEmail(), userDetailsDto.getPassword(), userDetailsDto.getEnabledStatus().getValue(),
userDetailsDto.getAccountNonExpiredStatus().getValue(), true,
userDetailsDto.getAccountNonLockedStatus().getValue(),
roles.stream().map(role -> new SimpleGrantedAuthority(role.getId())).collect(Collectors.toList()));
this.modulePermissions = modulePermissions;
this.userType = userType;
this.userId = userDetailsDto.getId();
this.firstLogin = userDetailsDto.getIsFirstLoginStatus().getValue();
this.firstName = userDetailsDto.getFirstName();
this.lastName = userDetailsDto.getLastName();
this.contactNo = userDetailsDto.getContactNo();
}
public List<ModulePermission> getModulePermissions() {
return Collections.unmodifiableList(modulePermissions);
}
public UserType getUserType() {
return userType;
}
public Long getUserId() {
return userId;
}
public boolean isFirstLogin() {
return firstLogin;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getContactNo() {
return contactNo;
}
public void setFirstLogin(boolean firstLogin) {
this.firstLogin = firstLogin;
}
}
Now, to configure Spring Security to use my CustomUserDetailsService, I do the following in security configuration -
#Bean
public UserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(SuperAdminConstant.UrlConstant.ANT_MATCHER_PATH)
.userDetailsService(customUserDetailsService())
.formLogin(// further configuration)
}
And this works without any problems.
But notice that in CustomUserDetailsService, several database queries are executed even before the user has been authenticated successfully (This is because Spring Security has created a DaoAuthenticationProvider, which loads a UserDetails implementation (in my case, LoggedInUser), and perform various checks on that object AFTER it has been retrieved from a UserDetailsService (in my case , CustomUserDetailsService)).
Consider that a user has entered the correct username, but a wrong password. In that case, the high-level authentication flow would be -
CustomUserDetailsService would be called
First query is executed to verify username and load user details (UsernameNotFoundException is not thrown as username is correct)
Second query is executed to retrieve the roles
Third query is executed to retrieve module permissions
Fourth query is executed to retrieve user types
DaoAuthenticationProvider checks the password, finds it to be incorrect, and throws a BadCredentialsException.
So as can be seen, total 4 queries are executed EVEN BEFORE authentication process has completed, out of which only 1 is essential at this stage (the first query to verify username).
One solution to this problem can be to eliminate the use of UserDetailsService altogeather, and use a custom AuthenticationProvider instead.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Customize the authentication logic here, and retrieve
// user information only if everything is correct.
}
}
But going by this approach also means that I have to duplicate code and functionality provided by DaoAuthenticationProvider and AbstractUserDetailsAuthenticationProvider, which includes checking user account status flags manually (accountNonExpired, accountNonLocked etc.), and throwing exceptions.
So I was wondering weather it is possible to perform authentication logic in such a way that user information is retrieved only AFTER authentication succeeds, and most of the authentication logic provided by Spring Security can be resused.
Any ideas will be deeply appreciated.
You can write implementation of AuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LoggedInUser loggedInUser = (LoggedInUser)authentication.getPrincipal();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
// Set roles after authentication succeeds
loggedInUser.setRoles(roles);
loggedInUser.setModulePermissions(modulePermissions);
loggedInUser.setUserType(userType);
}
}
After the authentication succeeds, you can obtain logged in user from security context and set additional properties.

How to login by using my own Controller? [duplicate]

This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 5 years ago.
My app is a full RESTful style web application which provides JSON interfaces only(no web pages). I want to write my own controller that I can fully control the login process:
#GetMapping("/login")
public String login(#RequestParam String username,
#RequestParam String password,
HttpServletRequest req,
HttpServletResponse resp) {
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username, password);
try {
Authentication auth = authManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (AuthenticationException ex) {
return ex.getMessage();
}
return "success";
And this is my UserDetailsService:
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
if (!name.equals("whf")) {
throw new UsernameNotFoundException("not found");
}
// load roles
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("admin"));
return new User("whf", "pwd", authorities);
}
}
But when I access the URL that requires admin role, I got Access is Denied:
#GetMapping("/a")
#PreAuthorize("hasRole('admin')")
public String A(Principal principal) {
return "a";
}
Current user has been given the admin role. Why can't I access /a?
I've figured it out.
I missed the ROLE_ prefix at the step of granting authority.

Custom Validation for Duplicate UserName in DB

If you have better approach to handle custom Validation please let me know. I don't want service layer for this please.
Read below 5th option what I want.
I have
1 - IUserRepository -> bool IsUserRegistered(string userName);
2 - UserRepository with Method
readonly EFDBContainer _db = new EFDBContainer();
public bool IsUserRegistered(string userName)
{
return _db.Users.Any(d => d.UserName == userName);
}
3 - Ninject --> UserController is DI
public static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUserRepository>().To<UserRepositary>();
}
4 - UserController
private readonly IUserRepository _repository;
public ProfileController(IUserRepository repository)
{
_repository = repository;
}
Create Method on Controller
HttpPost]
public ActionResult Create(string confirmButton, User user)
{
if (ModelState.IsValid)
{
try
{
_repository.Create(user); --> This calling Create Method below before this EnsureValid is Called
return //Do Redirection
}
catch (RuleViolationException)
{
this.UpdateModelStateWithViolations(user, ViewData.ModelState);
}
}
return //to View;
}
Create Method from Repository
public void Create(User user)
{
user.EnsureValid(); --> Go to User object and do validation
//Add object to DB
}
5 - What I want:
Here I want DI so that I can call 1st IsUserRegistered interface method on User object
IsUserRegistered below is not working right now. I need a way to use the Interface
public partial class User: IRuleEntity
{
public List<RuleViolation> GetRuleViolations()
{
List<RuleViolation> validationIssues = new List<RuleViolation>();
if (IsUserRegistered(userName))
validationIssues.Add(new RuleViolation("UserName", UserName, "Username already exists. Please enter a different user name."));
return validationIssues;
}
public void EnsureValid()
{
List<RuleViolation> issues = GetRuleViolations();
if (issues.Count != 0)
throw new RuleViolationException("Business Rule Violations", issues);
}
}
Write your own validation attribute and add it to the user name.
See http://www.planetgeek.ch/2010/11/13/official-ninject-mvc-extension-gets-support-for-mvc3/. It explains how to inject dependencies into validators.
See also the sample application that comes with the Ninject MVC extension it has an example of a validator that has a dependency. https://github.com/ninject/ninject.web.mvc

Resources