I'm using the Acegi Security plugin for Grails, and authentication via LDAP.
The application logs show that on login, we can authenticate the user and get their roles via LDAP, but the login fails because the User Details cannot be found in the application's database.
Is there a way to auto create and save a basic User Details domain object if one doesn't already exist?
-- Update
Here are the relevant debug entries I am currently seeing
DEBUG populator.DefaultLdapAuthoritiesPopulator - Roles from search: [Role1, Role2, etc]
ERROR springsecurity.GrailsDaoImpl - User not found: MyUserName
DEBUG rememberme.TokenBasedRememberMeServices - Interactive login attempt was unsuccessful.
Sure.
You need to implement custom AuthProvider
SecurityConfig.groovy:
security {
providerNames = ['ldapAuthProvider']
}
Ldap Auth Provider:
import domain.user.AppUser
import org.apache.commons.codec.digest.DigestUtils
import org.apache.log4j.Logger
import org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl
import org.springframework.security.BadCredentialsException
import org.springframework.security.GrantedAuthority
import org.springframework.security.GrantedAuthorityImpl
import org.springframework.security.providers.UsernamePasswordAuthenticationToken
import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider
import org.springframework.security.userdetails.UserDetails
/**
* Authentication provider that checks user credentials against LDAP
*/
class LdapAuthProvider extends AbstractUserDetailsAuthenticationProvider {
private static final Logger log = Logger.getLogger(LdapAuthProvider.class)
def appUserService
/**
* Checks password hash stored in the session with password in authentication token.
*/
protected void additionalAuthenticationChecks(UserDetails details,
UsernamePasswordAuthenticationToken authentication) {
if (details.password != DigestUtils.md5Hex(authentication.credentials)) {
throw new BadCredentialsException(details.username)
}
}
/**
* Retrieves user from LDAP,
* checks credentials,
* updates local copy of user data,
* returns user details.
*/
protected UserDetails retrieveUser(String login, UsernamePasswordAuthenticationToken authentication) {
AppUser.withTransaction {
log.debug("Trying to retrieve user \"$login\"...")
def password = authentication.credentials?.toString()
def ldapUser = appUserService.findLdapUser(login)
if (!(password && ldapUser?.authenticate(password))) {
log.debug("Can't authenticate \"$login\"")
throw new BadCredentialsException(login)
}
AppUser localUser = AppUser.findByLogin(login, [cache: true])
if (!localUser) {
log.debug("Can't authenticate \"$login\"")
localUser = appUserService.updateLocalUser(ldapUser)
}
log.debug("User \"$login\" is authenticated.")
def authorities = localUser.collectAuthorities().collect {String authority ->
log.debug("\thas right \"$authority\"")
new GrantedAuthorityImpl(authority)
}
def userDetails = new AppUser();
userDetails.setAssignedTemplate(localUser.assignedTemplate)
userDetails.setFullName(localUser.getFullName())
userDetails.setLogin(localUser.getLogin())
userDetails.setEmail(localUser.getEmail())
userDetails.setDisabled(localUser.getDisabled())
userDetails.setManager(localUser.getManager())
userDetails.setRoles(new HashSet(localUser.getRoles()))
log.debug("Retrieving user \"$login\" is completed.")
return new GrailsUserImpl(userDetails.login, DigestUtils.md5Hex(password), true, true, true, true,
authorities.toArray(new GrantedAuthority[authorities.size()]), userDetails)
}
}
}
And in appUserService.updateLocalUser(ldapUser) you need create/modify your Domain object and persist in database.
AppUser updateLocalUser(LdapUser ldapUser) {
def login = ldapUser.login
log.debug("Start updating local user ${login}...")
def localUser = AppUser.findByLogin(login, [cache: true]) ?: new AppUser()
if (localUser.id) {
log.debug("user $login was found in local DB")
if (localUser.disabled ^ ldapUser.isDisabled()) {
log.debug("...user ${login} has been ${localUser.disabled ? 'activated' : 'disabled'}...")
}
} else {
log.debug("user $login is new")
}
localUser.login = login
localUser.email = ldapUser.email
localUser.fullName = ldapUser.fullName ?: login
localUser.disabled = ldapUser.isDisabled();
localUser.roles?.clear()
ldapUser.memberOf.collect { Role.findByLdapName(it, [cache: true]) }.each {role ->
if (role) {
localUser.addToRoles(role)
}
};
localUser.save(flush: true)
log.debug("Update local user $login is complete.")
}
UPDATE #1
You can implement custom UserDetailsService:
package com.foo.bar;
import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService;
public class MyUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException, DataAccessException {
// lookup user and data
return new MyUserDetails(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, id, fullName); } }
Related
I am working on a Spring Boot application that take the username and password of an existing user on the system and then generates a JWT token. I copied it from a tutorial and I changed it in order to work with my specific use cases. The logic is pretty clear to me but I have a big doubt about how the user is authenticated on the system. Following I will try to explain you as this is structured and what is my doubt.
The JWT generation token system is composed by two different micro services, that are:
The GET-USER-WS: this microservice simmply use Hibernate\JPA to retrieve the information of a specific user in the system. Basically it contains a controller class calling a service class that itself calla JPA repository in order to retrieve a specific user information:
#RestController
#RequestMapping("api/users")
#Log
public class UserController {
#Autowired
UserService userService;
#GetMapping(value = "/{email}", produces = "application/json")
public ResponseEntity<User> getUserByEmail(#PathVariable("email") String eMail) throws NotFoundException {
log.info(String.format("****** Get the user with eMail %s *******", eMail) );
User user = userService.getUserByEmail(eMail);
if (user == null)
{
String ErrMsg = String.format("The user with eMail %s was not found", eMail);
log.warning(ErrMsg);
throw new NotFoundException(ErrMsg);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}
}
As you can see this controller contains an API that use the e-mail parameter (that is the username on the system) and return a JSON containing the information of this user.
Then I have a second microservice (named AUTH-SERVER-JWT) that is the one that call the previous API in order to obtain the user information that will be used to generate the JWT token. To keep the description as simple as possible it contains this controller class:
#RestController
//#CrossOrigin(origins = "http://localhost:4200")
public class JwtAuthenticationRestController {
#Value("${sicurezza.header}")
private String tokenHeader;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
#Qualifier("customUserDetailsService")
//private UserDetailsService userDetailsService;
private CustomUserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationRestController.class);
#PostMapping(value = "${sicurezza.uri}")
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
logger.info("Autenticazione e Generazione Token");
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
//final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final UserDetailsWrapper userDetailsWrapper = userDetailsService.loadCompleteUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetailsWrapper);
logger.warn(String.format("Token %s", token));
return ResponseEntity.ok(new JwtTokenResponse(token));
}
#RequestMapping(value = "${sicurezza.uri}", method = RequestMethod.GET)
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request)
throws Exception
{
String authToken = request.getHeader(tokenHeader);
if (authToken == null || authToken.length() < 7)
{
throw new Exception("Token assente o non valido!");
}
final String token = authToken.substring(7);
if (jwtTokenUtil.canTokenBeRefreshed(token))
{
String refreshedToken = jwtTokenUtil.refreshToken(token);
return ResponseEntity.ok(new JwtTokenResponse(refreshedToken));
}
else
{
return ResponseEntity.badRequest().body(null);
}
}
#ExceptionHandler({ AuthenticationException.class })
public ResponseEntity<String> handleAuthenticationException(AuthenticationException e)
{
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
}
private void authenticate(String username, String password)
{
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
/// ???
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (DisabledException e)
{
logger.warn("UTENTE DISABILITATO");
throw new AuthenticationException("UTENTE DISABILITATO", e);
}
catch (BadCredentialsException e)
{
logger.warn("CREDENZIALI NON VALIDE");
throw new AuthenticationException("CREDENZIALI NON VALIDE", e);
}
}
}
This class contains two method, the first one is used to generate a brand new JWT token and the second one it is used to refresh an existing JWT token. Consider now the first use case (generate a brand new JWT token) related to the createAuthenticationToken() method. This method take as input parmether the information related to the JWT token request: #RequestBody JwtTokenRequest authenticationRequest. Bascailly the JwtTokenRequest is a simple DTO object like this:
#Data
public class JwtTokenRequest implements Serializable
{
private static final long serialVersionUID = -3558537416135446309L;
private String username;
private String password;
}
So the payload in the body request will be something like this:
{
"username": "xxx#gmail.com",
"password": "password"
}
NOTE: in my DB I have a user having this username and password so the user will be retrieved and authenticated on the system.
As you can see the first effective operation that the createAuthenticationToken() method do is:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
Basically it is calling the authenticate() method defined in the same class passing to it the previous credential ("username": "xxx#gmail.com" and "password": "password").
As you can see this is my authenticate() method
private void authenticate(String username, String password)
{
Objects.requireNonNull(username);
Objects.requireNonNull(password);
try {
/// ???
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
catch (DisabledException e)
{
logger.warn("UTENTE DISABILITATO");
throw new AuthenticationException("UTENTE DISABILITATO", e);
}
catch (BadCredentialsException e)
{
logger.warn("CREDENTIAL ERROR");
throw new AuthenticationException(""CREDENTIAL ERROR", e);
}
}
Basically it is passing these credential to the authenticate() method defined into the injected Spring Security AuthenticationManager instance, by this line:
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
This method seems to be able to authenticate or not these credentials. And it seems to works fine because if I put a wrong username or password it goes into the CREDENTIAL ERROR case and it throw the AuthenticationException exception.
And here my huge doubt: why it works?!?! How is it possible? If you come back to the createAuthenticationToken() controller method you can see that it does these two operation in the following order:
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetailsWrapper userDetailsWrapper = userDetailsService.loadCompleteUserByUsername(authenticationRequest.getUsername());
It first perform the authenticate() method (that should check if theusername and password that was sent are correct), then call the service method that retrieve the user information.
Sho how the authenticate() method is able to check if the credential sent in the original payload are correct?
Usually, the implementation of AuthenticationManager is a ProviderManager, which will loop through all the configured AuthenticationProviders and try to authenticate using the credentials provided.
One of the AuthenticationProviders, is DaoAuthenticationProvider, which supports a UsernamePasswordAuthenticationToken and uses the UserDetailsService (you have a customUserDetailsService) to retrieve the user and compare the password using the configured PasswordEncoder.
There is a more detailed explanation in the reference docs about the Authentication Architecture.
I have to remove a login field from my User class and use email as a username in SecurityUtils
I've already changed j_username parameter in a frontend, but now the issue remains on a backend
public static String getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
String userName = null;
if (authentication != null) {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
userName = springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
userName = (String) authentication.getPrincipal();
}
}
return userName;
}
and as a result userName is null, becauseUserDetails and Authentication don't have email. How could I set the field email as a 'j_username'? I've tried this
How to login by email instead of username in spring security
solution but it's not enough since I use an anonymousUser
Also, I have an implementation of UserDetailsService but when debugging it's not called when being the anonymousUser
public class DomainUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public DomainUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(final String login) {
String lowercaseLogin = login.toLowerCase(Locale.ENGLISH);
Optional<User> userFromDatabase = userRepository.findOneByLogin(lowercaseLogin);
return userFromDatabase.map(user -> {
if (!user.getActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(lowercaseLogin,
user.getPassword(),
grantedAuthorities);
}).orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " +
"database"));
}
}
In order to achieve your goal you will have to control anonymous user behavior. I had that issue before and when the user is logged in the queries are working fine. As M. Denim suggested you should search by email here -> Optional<User> userFromDatabase = userRepository.findOneByEmail(lowercaseLogin);
But in case of anonymous user in getCurrentUserLogin() you have to write an if statement to return anonymous#localhost in case the userName = anonymousUser
here i share some code from my Spring Security Configuration class
.formLogin().loginPage("/login")
.usernameParameter("logInId").passwordParameter("password")
here i use 'logInId' parameter for login instead of default parameter....
i think you searching some thing like this .......
I have set claims in JWT token in the token provider. now I want to get claim value through authentication when API is hit.
I have checked in Principal, details, credential, authorities but I am not getting claims in any of them.
Claims claims = Jwts.claims().setSubject(authentication.getName());
claims.put(AUTHORITIES_KEY, authorities);
claims.put("userId", userRepo.findUserIdByUsername(authentication.getName()));
return Jwts.builder()
.setSubject(authentication.getName())
.setClaims(claims)
//.claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, SIGNING_KEY)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + ACCESS_TOKEN_VALIDITY_SECONDS*1000))
.compact();
I want to get "userId" claim from the authentication or any other way to get claims value from token.
This is how I read Claim from Token
private Claims getAllClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.error("Could not get all claims Token from passed token");
claims = null;
}
return claims;
}
I am using this for JWT
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
More detail here
Edit 1:
Adding Filter to get token from Request and Validate
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;
public class TokenAuthenticationFilter extends OncePerRequestFilter {
protected final Log logger = LogFactory.getLog(getClass());
private TokenHelper tokenHelper;
private UserDetailsService userDetailsService;
public TokenAuthenticationFilter(TokenHelper tokenHelper, UserDetailsService userDetailsService) {
this.tokenHelper = tokenHelper;
this.userDetailsService = userDetailsService;
}
#Override
public void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws IOException, ServletException {
String username;
String authToken = tokenHelper.getToken(request);
logger.info("AuthToken: "+authToken);
if (authToken != null) {
// get username from token
username = tokenHelper.getUsernameFromToken(authToken);
logger.info("UserName: "+username);
if (username != null) {
// get user
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (tokenHelper.validateToken(authToken, userDetails)) {
// create authentication
TokenBasedAuthentication authentication = new TokenBasedAuthentication(userDetails);
authentication.setToken(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}else{
logger.error("Something is wrong with Token.");
}
}
chain.doFilter(request, response);
}
}
It should help.
You should be able to retrieve a claims like this within your controller
var identity = HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
IEnumerable<Claim> claims = identity.Claims;
// or
identity.FindFirst("ClaimName").Value;
}
If you wanted, you could write extension methods for the IPrincipal interface and retrieve claims using the code above, then retrieve them using (for example)
HttpContext.User.Identity.MethodName();
For completeness of the answer. To Decode the JWT token let's write a method to validate the token and extract the information.
public static ClaimsPrincipal ValidateToken(string jwtToken)
{
IdentityModelEventSource.ShowPII = true;
SecurityToken validatedToken;
TokenValidationParameters validationParameters = new TokenValidationParameters();
validationParameters.ValidateLifetime = true;
validationParameters.ValidAudience = _audience.ToLower();
validationParameters.ValidIssuer = _issuer.ToLower();
validationParameters.IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appSettings.Secret));
ClaimsPrincipal principal = new JwtSecurityTokenHandler().ValidateToken(jwtToken, validationParameters, out validatedToken);
return principal;
}
Now we can validate and extract the Claims by using:
ValidateToken(tokenString)?.FindFirst("ClaimName")?.Value
You should note that the ValidateToken method will return null value if the validation fails.
Using Spring Security 5 you can use #AuthenticationPrincipal org.springframework.security.oauth2.jwt.Jwt token as parameter in your controller method. And then call token.getClaims()
It would be recommended to refer the blog given below. It explained how the JWT token works in spring boot
https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
List out all the claims using JWT
private void listClaimUsingJWT(String accessToken) {
try {
SignedJWT signedJWT = SignedJWT.parse(accessToken);
JWTClaimsSet claimsSet= signedJWT.getJWTClaimsSet();
Map<String,Object> myClain =claimsSet.getClaims();
String[] keySet = myClain.keySet().toArray(new String[0]);
Log.d("JWT_Claims", "loadAllOptionalClaim JWT keySetSize "+keySet.length);
for (String s : keySet) {
Log.d("JWT_Claims", "loadAllOptionalClaim JWT key ==> " + s + " ====> " + myClain.get(s));
}
} catch (Exception e) {
e.printStackTrace();
}
}
If it is in quarkus, we can get it by injecting JSONWebToken:
/**
* Injection point for the ID Token issued by the OpenID Connect Provider
*/
#Inject
#IdToken
JsonWebToken idToken;
In Java, Keys for claim in keycloak provided by JSONWebToken can be accessed via getClaimNames() method. Following is an example:
Set<String> allClaims = this.idToken.getClaimNames();
for (String claimName: allClaims) {
System.out.println("Claim name: " + claimName);
}
Ref: https://quarkus.io/guides/security-openid-connect-web-authentication
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 have this CustomAuthenticator for user with Errai Security:
public CustomAuthenticator extends BaseAuthenticator {
#Override
public void authenticate() {
String userId = loginCredentials.getUserId();
String password = loginCredentials.getPassword();
User user = userDAO.fetchUserByName(userId);
if (!BCrypt.checkpw(password, user.getPasswordHash())) {
setStatus(AuthenticationStatus.FAILURE);
} else {
// Add to IDM
IdentityQuery<UserImpl> query
= partitionManager.createIdentityManager().createIdentityQuery(UserImpl.class);
query.setParameter(UserImpl.LOGIN_NAME, user.getUsername());
List<UserImpl> result = query.getResultList();
org.picketlink.idm.model.basic.Role trial = new org.picketlink.idm.model.basic.Role("TRIAL");
if (result.isEmpty()){
UserImpl account = new UserImpl(user);
partitionManager.createIdentityManager().add(account);
partitionManager.createIdentityManager().updateCredential(account, new Password(password));
partitionManager.createIdentityManager().add(trial);
BasicModel.grantRole(partitionManager.createRelationshipManager(), account, trial);
IdentityQuery<UserImpl> q
= partitionManager.createIdentityManager().createIdentityQuery(UserImpl.class);
q.setParameter(UserImpl.LOGIN_NAME, user.getUsername());
UserImpl u = q.getResultList().iterator().next();
setStatus(AuthenticationStatus.SUCCESS);
setAccount(u);
} else {
setStatus(AuthenticationStatus.SUCCESS);
setAccount(result.iterator().next());
}
userEvent.fire(user);
}
}
Even I check the seAccount Account to be set is ok, I am not sure if the Roles is persisted at list at the Picketlink side; because the response of the call:
Caller<AuthenticationService> authServiceCaller;
The Errai Security User returned although not null, the names is "ANONYMOUS" and role is "NOBODY" I'm not sure what's happening here.
Update:
The login(username, password) method returns the correct User and Role, but getUser() does not. This is the issue.