I have been following Baeldung's Spring 2FA tutorial to implement 2FA. I have created a CustomAuthenticationProvider as instructed, however it's not behaving as expected.
The odd thing is that after login, a username format that I'm not familiar with is displayed when using Principal.getName():
com.appname.models.User#69080b62
As parts of the application rely on this for fetching details, this isn't appropriate but I'm struggling to understand where I've gone wrong. I have done some research but without the correct nomenclature and the name of the format, I'm struggling to find the appropriate result.
public Authentication authenticate(Authentication auth) throws AuthenticationException {
User user = userRepository.findByUsername(auth.getName());
if(user == null) {
throw new BadCredentialsException("Invalid username or password");
}
if(user.getTwoFactor()) {
//as per tutorial
}
//this returns the "correct" username
System.out.println(user.getUsername());
final Authentication result = super.authenticate(auth);
//I suspect it's here that the issue is occurring, though any pointers in the right direction would be appreciated
return new UsernamePasswordAuthenticationToken(user, result.getCredentials(), result.getAuthorities());
}
I'm expecting the actual username rather than...however it is currently being returned - i.e. an email address of a user.
I have "solved" the problem by changing the last few lines to:
final Authentication result = super.authenticate(auth);
UserDetails userDetails = userDetailsService.loadUserByUsername(auth.getName());
return new UsernamePasswordAuthenticationToken(userDetails,
result.getCredentials(), userDetails.getAuthorities());
...where userDetailsService points to a simple implementation of the Spring Security UserDetailsService which returns a Spring Security UserDetails object, like so:
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username);
if(user == null) throw new UsernameNotFoundException(username);
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (Role role : user.getRoles()) {
grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), user.getEnabled(), user.getNonExpired(),
user.getCredentialsNonExpired(), user.getNonLocked(), grantedAuthorities);
}
This works elsewhere in my application so I figured it might work here as well. I believe I could have left the final argument as result.getAuthorities(). I think I can also refactor so I'm not hitting the database twice, but for now I'm just glad that it works.
I'm not 100% sure why my relatively simple User model would not return the username as the Principal name, it may be that there is some more work which should be done to my User object to explicitly mark the username String as the principal name.
If anyone is interested in any further updates, or can provide any more information for anyone else experiencing uncertainty on this issue, please leave a comment or provide another (likely better) answer.
Related
I want to implement user login/signup using username, password and project (additional field). It is working with username and password but unable to implement to add additional field (project) since I am new to spring security.
I am able to signup with email, username, password and project.
I have added Project in UserPrincipal as shown in below code.
I need to implement custom authentication to add project during login but I am unable to figure out how to proceed further. Can anybody help please? i have already checked other solutions but did not figure out to implement.
If I understood correctly, you want to just add this project info to a User, during login/sign-up.
As #HarshVerma pointed out, in Spring it's only the login and the password you need to autheticate.
You could implement user's project as a JWT claim:
Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.claim("project", myProject)
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
and then retreive it to authorize accordingly:
Claims claims = new DefaultClaims();
try{
claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(jwt).getBody();
} catch (SignatureException e){
// signature exception
}
Make the following changes to the auth controller. Check if username owns the project before setting the authentication in the context. This feels a little hacky though :
public class AuthController {
...
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsernameOrEmail(),
loginRequest.getPassword()
)
);
// checking if user owns the project
if (usernameOwnsProject(username, project)) {
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
} else {
// throw error
}
}
...
}
I am doing a project on library management system in spring boot security.
In order to calculate the fines for the issued books according to the roles i wan the current user role after borrowing a book.
Current user name, role book_id and fine will be stored in other table.
I am able to get the current users username, but not able to get role the current user.
Could someone please help me out!
//Part of Controller class
#RequestMapping("/homepage/borrowBook")
public String addBookings(Bookings bk, HttpServletRequest rqst) {
rqst.setAttribute("mode", "MODE_BORROW");
return "homepage";
}
#PostMapping("/homepage/save-borrow")
public String saveBorrow(Bookings bk, HttpServletRequest rqst, Authentication auth) {
rqst.setAttribute("mode", "MODE_BORROW");
if (BookRepo.exists(bk.getBook_id())) {
bk.setUser(auth.getName());
/////here i want the current user authority to be saved/checked.
bookingsRepo.save(bk);
return "homepage";
} else {
rqst.setAttribute("error", "Book doesn't exist");
return "homepage";
}
}
You can use Authentication.getAuthorities() to get the roles of the currently logged in user.
You can get the authorities using the SecurityContextHolder or through the inject Authentication object at your controller.
Find below through the SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Collection<SimpleGrantedAuthority> list = (Collection<SimpleGrantedAuthority>) auth.getAuthorities();
for (SimpleGrantedAuthority permission : list) {
System.out.println(permission.getAuthority());
}
If you need any other information about the logged in user, you can access the UserDetails as follows
User userDetails = (User) auth.getPrincipal();
I'm using spring security in my spring boot app to provide user functionality. I've spent quiet some time searching for an answer to the problem, but did only find solutions for people using xml-based configurations.
My set-up is very similar to this: http://www.baeldung.com/spring-security-track-logged-in-users (alternative method at the bottom).
This is my SecurityConfiguration:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin().defaultSuccessUrl("/home.html", true)
//.and().exceptionHandling().accessDeniedPage("/home")
.and().authorizeRequests().antMatchers("/editor").hasAnyAuthority("SUPERUSER")
.and().authorizeRequests().antMatchers("/editor").hasAnyAuthority("ADMIN")
.and().authorizeRequests().antMatchers("/").permitAll().anyRequest().authenticated()
.and().formLogin().loginPage("/login").permitAll()
.and().authorizeRequests().antMatchers("/static/**").permitAll()
.and().logout().permitAll().logoutSuccessUrl("/login").logoutUrl("/logout").deleteCookies("JSESSIONID")
.and().csrf().disable();
http.sessionManagement().invalidSessionUrl("/login").maximumSessions(1).sessionRegistry(sessionRegistry()).expiredUrl("/login");
}
This is where i call the sessionRegistry:
public List<String> getAllLoggedUsernames() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
// System.out.println("All Principals: " + sessionRegistry.getAllPrincipals());
List<String> allUsernames = new ArrayList<String>();
System.out.println(allUsernames.size());
for (final Object principal : allPrincipals) {
if (principal instanceof SecUserDetails) {
final SecUserDetails user = (SecUserDetails) principal;
//Make sure the session is not expired --------------------------------------------------▼
List<SessionInformation> activeUserSessions = sessionRegistry.getAllSessions(principal, false);
if (!activeUserSessions.isEmpty()) {
allUsernames.add(user.getUsername());
System.out.println(user.getUsername());
}
}
}
return allUsernames;
}
Now when I try to get the currently logged-in user i get it correctly like that:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
My sessionRegistry is defined as a Bean the following way:
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
It is left to say that I call the getAllLoggedUsernames() from a controller via a service much like this:
#Autowired
private SecUserDetailService service;
And later in a #RequestMapping function:
service.getAllLoggedUsernames();
And the list received there is always empty, no matter how many users are actually logged in.
Now my guess from other questions asked here would be that somehow my application gets loaded twice or that my bean setup is messed up. I kind of think that the #Autowired does not work, since I think the Service needs some kind of context information?
I'm really new to Dependency injection though, so it's kinda hard to get everything correct.
Thanks for any help in advance!
Edit - Minor clarifications
Solved: There was a type error, the if statement in the getAllLoggedUsers() method always resolved to false as the Object is not an instance of my own UserDetails class, but of org.springframework.security.core.userdetails.User!
I wrote functionality using Spring Security SwitchUserFilter. In application I can switch user using /j_spring_security_switch_user?j_username=xxx URL and go back to previous using /j_spring_security_exit_user.
I also implemented several methods that depends on fact of switching user, so I want to write unit tests for them.
Therefore my question is how can I switch user in jUnit tests environment?
I wrote method which is preparing user with SwitchUserGrantedAuthority and log him in. It seems working fine for my testing purposes, but any tips and comments would be very appreciated.
#SuppressWarnings({ "rawtypes", "unchecked" })
private User logAdminAsUser(User admin, String roleName) {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(admin, null, "ROLE_ADMIN"));
Authentication adminAuth = SecurityContextHolder.getContext().getAuthentication();
SwitchUserGrantedAuthority switchUserGrantedAuthority =
new SwitchUserGrantedAuthority("ROLE_ADMIN", adminAuth);
List authorities = new LinkedList();
authorities.add(switchUserGrantedAuthority);
User user = populator.storeUser("ROLE_USER");
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken(user, null, authorities));
return user;
}
If you want an integrational test, you should consider using a custom http client, or if your test logic depends on it, even GUI drivers like Selenium.
If we are talking about unit tests, refer to Springs
http://spring.io/blog/2014/05/07/preview-spring-security-test-method-security
documentation, they support testing heavily, #WithMockUser annotation appears to be what you are looking for, it allows you to specify with which role or user this test should be runned.
I used this:
private void switchUser(User user, String roleName)
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Collection<GrantedAuthority> authorities =
new ArrayList<>();
GrantedAuthority ga = new SimpleGrantedAuthority(roleName);
authorities.add(ga);
Authentication result = new UsernamePasswordAuthenticationTokenExt(
user,
authentication.getCredentials(),
null,
System.currentTimeMillis()
);
SecurityContextHolder.getContext().setAuthentication( result );
}
where User is the new user, and the roleName is the new authority to set (of course this method can be modified get more params, etc.)
The question is very simple. I'd like to restrict user access with same login from different machines/browsers: only one live user session is possible.
Apache shiro library is used for user authentification and managment.
Of course this could be done using simple synchornized maps and etc. But the question is: Has Apache Shiro special mechanisms for that or not?
Another variant of this question: how to reveice the list of all subjects who are logged in the system using apache shiro?
UPD:
To clarify my question. My desire is to have some code like this (I known, that there isn't such class exception, but the idea must be more clean):
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(login, password);
try {
currentUser.login(token);
} catch (AlreadyAuthenticatedException aae) {
errorMsg = "You should logoff on another machine!";
}
The Shiro sessions are stored in SessionDAO with sessionId as keys. Without extra effort you cannot access a session by a principal (user name). However, you could extend DefaultSecurityManager and check all active sessions by SessionDAO.getActiveSessions.
The following codes could be a simple example (suppose you are not using WebSubject):
public class UniquePrincipalSecurityManager extends org.apache.shiro.mgt.DefaultSecurityManager {
#Override
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
String loginPrincipal = (String) token.getPrincipal();
DefaultSessionManager sm = (DefaultSessionManager) getSessionManager();
for (Session session : sm.getSessionDAO().getActiveSessions()) {
SimplePrincipalCollection p = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (p != null && loginPrincipal.equals(p.getPrimaryPrincipal())) {
throw new AlreadyAuthenticatedException();
}
}
return super.login(subject, token);
}
}