How to add additional details to Spring Security userdetails - spring

I want to add additional information to userdetails like user's Ip address. Is there any way to achieve this? I tried to create a new CustomSpringUser class but the problem is how can i get this information from Authentication object. Is there any other way to store additional information for authenticated user?
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
My custom user class;
public class CustomSpringUser extends org.springframework.security.core.userdetails.User {
public String ip;
public CustomSpringUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public CustomSpringUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities, String ip) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.ip= ip;
}
}
Edit: I found that we can add additional information for Authentication but I couldn't found how to do that.
http://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/core/Authentication.html#getDetails()
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
try {
AppUser appUser = new AppUser();
appUser.setUsername(userName);
AppUser domainUser = genericDao.getByTemplate(appUser).get(0);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
List<String> roles = new ArrayList<String>();
roles.add(domainUser.getRole().getName());
return new CustomSpringUser(
domainUser.getUsername(),
domainUser.getPassword().toLowerCase(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getGrantedAuthorities(roles),
***domainUser.getAccount().getIdentificationId())*** ;
} catch (Exception e) {
genericLogger.saveLog(Logger.Status.ERROR, "Couldn't login", e);
throw new RuntimeException(e);
}
}

To get the UserDetails from the Authentication object/instance use the getPrincipal() method. The getDetails() method is to be used to get additional information about the user (which in general will be an instance of WebAuthenticationDetails).
Links
Authentication javadoc
Authentication.getDetails() javadoc
Authentication.getPrincipal() javadoc

Related

How to provide custom UserDetails with additional fields for testing a secured controller method?

Assume I have the following #WebMvcTest and #RestController in a Spring boot applcation (version 2.4.2).
// the test
#Test
#WithUserDetails
public void should_return_ok() throws Exception {
mockMvc.perform(get("/api/products").andExpect(status().isOk());
}
// the controller
#GetMapping(path = "/api/products")
public ResponseEntity<List<Product>> getProducts(#AuthenticationPrincipal CustomUserDetails userDetails) {
List<Product> products = productService.getProductsByUserId(userDetails.getUserId());
return ResponseEntity.ok(products);
}
I also provided a CustomUserDetails class which adds a userId.
#Getter
#Setter
public class CustomUserDetails extends User {
private static final long serialVersionUID = 5540615754152379571L;
private Long userId;
public CustomUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public CustomUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}
I understand that Spring provides the #WithUserDetails annotation to provide an adequate object for testing. And this also allows specifying a custom username, password, etc. However I don't know how I could provide the userId which is necessary so that the controller method can extract it from the CustomUserDetails object.
You can create your own custom UserDetails object in your test class and do the following:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
CustomUserDetails customUserDetails = new CustomUserDetails(...);
mockMvc.perform(get("/api/products").with(user(customUserDetails))).andExpect(status().isOk());
In your implementation of UserDetailsService you should return your instance of UserDetails. For example:
#Override
public UserDetails loadByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Username " + username + " not found");
}
CustomUserDetails customUserDetails = new CustomUserDetails(user);
customUserDetails.setUserId(user.getUserId());
return customUserDetails;
}
public class CustomUserDetails implements UserDetails {
private final Long userId;
private final User user;
...constructors
...getters and setters
}
In your code, you can cast the Authentication object to your CustomUserDetails.
CustomUserDetails customUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication();
Long userId = customUserDetails.getUserId();

How to access user details in Controller using Spring Security?

I need to access currently logged-in user details (id or email address) in Controller. This is how I am trying to do so right now, and this doesn't work.
ApplicationUser is an #Entity in database.
UserDetailsService:
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
private ApplicationUserRepository applicationUserRepository;
public UserDetailsServiceImpl(ApplicationUserRepository applicationUserRepository) {
this.applicationUserRepository = applicationUserRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
ApplicationUser applicationUser = applicationUserRepository.findByUsername(username);
if (applicationUser == null) {
throw new UsernameNotFoundException(username);
}
return builtCustomUser(applicationUser);
}
private User builtCustomUser(ApplicationUser applicationUser) {
String username = applicationUser.getUsername();
String password = applicationUser.getPassword();
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
MyUser myUser = new MyUser(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, emptyList());
return myUser;
}
}
Custom User class:
public class MyUser extends User implements UserDetails {
public MyUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}
That's how I am trying to access it in Controller:
MyUser mu = (MyUser) authentication.getPrincipal();
And this is error:
java.lang.ClassCastException: class java.lang.String cannot be cast to class MyUser
On this code, actual type of Authentication is UsernamePasswordAuthenticationToken, and return type of getPrincipal() is String, username.
You can set any other Authentication implementation instead of UsernamePasswordAuthenticationToken to SecurityContext, and principal type is free(so you can set MyUser), too.

How to perform Auth0 JWT token user Role-based authorization

I'm writing Spring Boot REST API, and I'm using JWT tokens. Now, I'm trying to create role-based authorization.
This is the tutorial/implementation that I'm using.
https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
I expanded this implementation with additional Role entity, and added #ManyToMany mapping to ApplicationUser Entity.
Now, as far as I understands, user roles should be added to token (during its creation).
So, this is an existing code:
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(HMAC512(SECRET.getBytes()));
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
I guess user roles should be added there. There is a function:
withArrayClaim(String Name, String[] items)
And there's my first problem: I'm not sure how to properly add this.
Then, is this fragments, which as far as I understand is place where token is verified:
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
What's bother me is fragment:
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
I don't understand why there is null (Inteliij highlights it as 'credentials') and this new ArrayList. Is there a place, where I should fetch roles from token, and add them?
I know, it's kinda broad scope question, but i couldn't find other solutions.
Or mayby there is an easier way to create simple JWT token authenitcation/authorization (role based).
Looking forward for your answers!
EDIT:
Or mayby is there more simple solutioni - not keeping user roles inside key - but only adding them in this 'second' part where null and new ArrayList is?
Just create the granted authorities based in the user roles and authenticate the user with it. Then the authenticated user principal will contain the roles.
Simple example:
UserEntity userEntity = userRepository.findUserByEmail(user); // this depends of course on your implementation
if (userEntity == null) return null;
List<RoleEntity> roles = userEntity.getRoles();
Collection<GrantedAuthority> authorities = new HashSet<>();
roles.forEach((role) -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return new UsernamePasswordAuthenticationToken(user, null, authorities);
Even better, you can create a UserPrincipal that implements UserDetails from spring security.
public class UserPrincipal implements UserDetails {
private static final long serialVersionUID = 1L;
private final UserEntity userEntity;
public UserPrincipal(UserEntity userEntity){
this.userEntity = userEntity;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new HashSet<>();
// Get user Roles
Collection<RoleEntity> roles = userEntity.getRoles();
if(roles == null) return authorities;
roles.forEach((role) -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return authorities;
}
#Override
public String getPassword() {
return this.userEntity.getEncryptedPassword();
}
#Override
public String getUsername() {
return this.userEntity.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
And to use it:
UserEntity userEntity = userRepository.findUserByEmail(user);
if (userEntity == null) return null;
UserPrincipal userPrincipal = new UserPrincipal(userEntity);
return new UsernamePasswordAuthenticationToken(userPrincipal, null, userPrincipal.getAuthorities());

Spring cache try and cache user

I am trying to cache the UserDetails loadUserByUsername(String username)
the problem is that after the caching the results comes with the correct user but
the password is always set to null but it was not null when cached
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Cacheable(value="usersLogged" ,key="#username" ,unless="#result.password==null")
#Override
public org.springframework.security.core.userdetails.User loadUserByUsername(
String username) throws UsernameNotFoundException {
try {
// User user = userRepository.getUserByEmail(username); Switch to id
// token base
User user = userRepository.findOne(username);
if (user == null) {
throw new UsernameNotFoundException(
"Invalid username/password.");
}
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = user.isActive();
String userN = user.getId(); // the suer is in the system
String pass = user.getPassword();
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(user.getRole().toString());
org.springframework.security.core.userdetails.User userBuild = new org.springframework.security.core.userdetails.User(
userN, pass, user.isEnabled(), accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities);
return userBuild;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
// throw new
// UsernameNotFoundException("Invalid username/password.");
}
}
}
Seems like spring cache have problems caching when public visibility
password is protected
In the manual
When using proxies, you should apply the cache annotations only to methods with public visibility. If you do annotate protected, private or package-visible methods with these annotations, no error is raised, but the annotated method does not exhibit the configured caching settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods as it changes the bytecode itself

UserDetailsService config for properly getting user

I create this topic from my previous one Get authenticated user entity Spring MVC where I asked question about properly getting authenticated user entity. I adviced that Principal object (for example, on my view <sec:authentication property="principal.customFieldName" />) can has access to my custom fields if my UserDetailsService configuration is right. Does my UserDetailsService configured properly to accomplish this functionality?
#Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private static final Logger logger = Logger.getLogger(UserDetailsServiceImpl.class);
#Autowired
#Qualifier("hibernateUserDao")
private UserDAO userDAO;
#Override
#Transactional(readOnly = true)
public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
UserDetails user = userDAO.findByLogin(userName);
if (user == null) {
logger.error("User was not found! Input login: " + userName);
}
return buildUserFormUserEntity(user);
}
#Transactional(readOnly = true)
private org.springframework.security.core.userdetails.User buildUserFormUserEntity(UserDetails userDetails) {
boolean enableStatus = userDetails.isEnabled();
String userName = userDetails.getLogin();
String password = userDetails.getPassword();
boolean enabled = enableStatus;
boolean accountNonExpired = enableStatus;
boolean credentialsNonExpired = enableStatus;
boolean accountNonLocked = enableStatus;
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(userDetails.getRole()));
User springSecurityUser = new User(userName, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
return springSecurityUser;
}
public UserDAO getUserDAO() {
return userDAO;
}
public void setUserDAO(UserDAO userDAO) {
this.userDAO = userDAO;
}
}
I think you need some additional steps to be able succesfully use
<sec:authentication property="principal.customFieldName" />
on some page:
Add your custom user object that implements org.springframework.security.core.userdetails.UserDetails interface. The simpliest way to do it is to extend existing org.springframework.security.core.userdetails.User class: class CutomUser extends User
Add your customFieldName property to CutomUser class.
Use CutomUser as a return type in your UserDetailsServiceImpl.loadUserByUsername(...) method. Do not forget to fill customFieldName at this moment.

Resources