Spring security context manual authentication giving strange results - spring

I have added spring authentication manually in rest/soap services calls by intercepting incoming rest/soap requests using HandlerInterceptor and SoapHandler. I send my logged in username from client to web service by adding it in soap/rest header. I intercept incoming request and set manual authentication. When I found authentication is already there, I skip setting it. I am assuming that for every new rest/soap request there will be no authentication on web service(server side). It's giving me username which is not of current user for new request.
public UserDetails getUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetails user= null;
if (auth != null && !(auth instanceof AnonymousAuthenticationToken)) {
// userDetails = auth.getPrincipal()
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username = null;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
user= (UserDetails ) principal;
} else {
username = principal.toString();
}
}
return user;
}
public void setUser(String username) {
// creating spring security context manually
try {
// Must be called from request filtered by Spring Security,
// otherwise SecurityContextHolder is not updated
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<SimpleGrantedAuthority>();
Authentication authentication;
authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !(authentication instanceof AnonymousAuthenticationToken)) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
UserDetails user;
String uname;
if (principal instanceof UserDetails) {
user= (User) principal;
uname = user.getUsername();
} else {
uname = principal.toString();
}
LOGGER.info("Found username in Spring context: " + uname);
} else {
LOGGER.info("Spring context not found: ");
LOGGER.info("Setting manual authentication.. Username: " + username);
// grantedAuthorities.add(new SimpleGrantedAuthority("USER"));
UserDetails contextUser = new User(username, username, true, true, true, true,
grantedAuthorities, null);
authentication = new UsernamePasswordAuthenticationToken(contextUser, username, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
SecurityContextHolder.getContext().setAuthentication(null);
LOGGER.error("Failure in creating spring security context authentication", e);
}
}

It is because SecurityContext is stored in the ThreadLocal and you have never clear it from the ThreadLocal after the web service thread completes processing the request , which means if the same thread is used to process the next request , it still keeps SecurityContext of the previous request.To be precise , it always keep the user who is the first to use that thread in your case.
The quick fix is that you have to clear SecurityContext after completing each request :
SecurityContextHolder.clearContext();

Related

SecurityContextHolder authentication object not available to subsequent requests from the client

Inside getUserObject() method we are not able to get Authentication object. It's available for 1st request only. But its setting to null for subsequent requests from client. So please help me to configure it properly so that its available for all the requests calls.
I am not sure how to configure inside configure method in AuthConfig.java so that authentication object would be available for all the requests chain
AuthConfig.java:
#Configuration
#EnableWebSecurity
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/callback", "/", "/auth0/authorize", "/resources/**", "/public/**", "/static/**",
"/login.do", "/logout.do", "/thankYou.do", "/customerEngagement.do",
"/oldCustomerEngagement.do", "/registerNew.do", "/forgotPassword.do", "/checkMongoService.do",
"/reset.do", "/rlaLogin.do", "/fnfrefer.do", "/thankYouLeadAggregator.do", "/referral")
.permitAll()
.anyRequest().authenticated().and().
logout()
.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler());
}
------------------------------------------------------------------------------
AuthController.java:
#RequestMapping(value = "/callback", method = RequestMethod.GET)
public void callback(HttpServletRequest request, HttpServletResponse response)
throws IOException, IdentityVerificationException {
try {
Tokens tokens = authenticationController.handle(request, response);
DecodedJWT jwt = JWT.decode(tokens.getIdToken());
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(jwt.getSubject(), jwt.getToken(), grantedAuths);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
response.sendRedirect(config.getContextPath(request) + "/loancenter/home.do");
} catch (Exception e) {
LOG.info("callback page error");
response.sendRedirect(config.getContextPath(request) + "/loancenter");
}
}
--------------------------------------------------------------------------------
HomeController.java:
#Controller
public class DefaultController implements InitializingBean {
#RequestMapping(value = "home.do")
public ModelAndView showCustomerPage(HttpServletRequest req, HttpServletResponse res, Model model) {
ModelAndView mav = new ModelAndView();
try {
User user = getUserObject(req);
if(user==null) {
LOG.info("User not found in session");
mav.setViewName(JspLookup.LOGIN);
return mav;
}
} catch (Exception e) {
LOG.error("Exception in Home page ", e);
}
return mav;
}
protected User getUserObject(HttpServletRequest request) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LOG.info("authentication::{}", authentication);
User user = null;
if (authentication == null) {
return user;
}
if (authentication.getPrincipal() instanceof User) {
user = (User) authentication.getPrincipal();
LOG.info("User already authenticated and logging :{}", user.getEmailId());
sendUserLoginEmailToLO(user);
} else {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
user = userProfileDao.findByUserEmail(jwt.getClaims().get("email").asString());
if (user != null) {
LOG.info("First time authentication:{}", user.getEmailId());
boolean auth0EmailVerified = jwt.getClaims().get("email_verified").asBoolean();
LOG.info("First time authentication email verified flag from auth0:{}", auth0EmailVerified);
LOG.info("First time authentication email verified flag from nlc:{}", user.getEmailVerified());
if (BooleanUtils.isFalse(user.getEmailVerified()) && auth0EmailVerified) {
LOG.info("Email is verified in Auth0, updating email_verified flag to true in DB for userId: {}",
user.getId());
userProfileDao.verifyEmail(user.getId());
LOG.info("First time authentication updated email verified flag in nlc db:{}", user.getEmailId());
}
if (user.getNewEmailVerified() != null && BooleanUtils.isFalse(user.getNewEmailVerified())) {
LOG.info("The user is verifying his email: set his verified to true");
userProfileDao.verifyNewEmail(user.getId());
}
Authentication auth = new UsernamePasswordAuthenticationToken(user, jwt.getToken(),
token.getAuthorities());
messageServiceHelper.checkIfUserFirstLogin(user);
LOG.info("Authentication provided for user : {}", user.getEmailId());
LOG.debug("Auth object constructed : {}", auth);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
sendUserLoginEmailToLO(user);
}
}
return user;
}
}

How to create multiple implementations of UserDetailsService in Spring Boot

I want to customize login API in spring boot. For a single kind of user, I created a implementation of UserDetailsService and it worked perfectly fine. Now, I want to create 3 different kinds of users, i.e., 3 different authorities. I don't think a single implementation can help me here. If I create 3 different implementations, and try using #Qualifier, how do I call a specific implementation ?
Any sort of help is appreciated! Below is the code for Login Endpoint of single kind Of user.
private static Logger logger = LogManager.getLogger();
#Value("${jwt.expires_in}")
private int EXPIRES_IN;
#Autowired
AuthenticationManager authManager;
#Autowired
TokenHelper tokenHelper;
#Autowired
ObjectMapper objectMapper;
#Autowired
PrincipalRepository principalRepository;
private boolean isAuthenticated(Authentication authentication) {
return authentication != null && !(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated();
}
#PostMapping("/principal")
public ResponseEntity<Object[]> loginPrincipal(#RequestParam(name ="username") String username,
#RequestParam(name ="password") String password){
logger.info("In login api");
if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
logger.error("Invalid Request!");
return ResponseEntity.badRequest().header("reason", "bad request").body(null);
}
UsernamePasswordAuthenticationToken authReq =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authManager.authenticate(authReq);
boolean isAuthenticated = isAuthenticated(authentication);
if (!isAuthenticated) {
logger.error("Not authenticated");
return ResponseEntity.badRequest().body(null);
}
Principal principal = null;
try {
principal = principalRepository.findByUserName(username);
}catch(Exception e) {
logger.error("Couldn't retrieve user");
return ResponseEntity.badRequest().header("reason", "username not found").body(null);
}
String jwt = tokenHelper.generateToken( username );
SecurityContextHolder.getContext().setAuthentication(authentication);
UserTokenState userTokenState = new UserTokenState(jwt, EXPIRES_IN);
return ResponseEntity.accepted().body(new Object[] {userTokenState, principal.getPrincipalID()});
}
Below is the code for UserDetailsService Implementation:
#Service
public class UserDetailServiceImpl implements UserDetailsService {
#Autowired
private PrincipalRepository principalRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("Loading user from db");
Principal principal = principalRepository.findByUserName(username);
if( principal == null){
System.out.println("User not found");
throw new UsernameNotFoundException("No user found. Username tried: " + username);
}
Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_PRINCIPAL"));
System.out.println("All done");
return new org.springframework.security.core.userdetails.User(principal.getUserName(), principal.getPassword(), grantedAuthorities);
}
}
Here, I am fetching a principal from db, because this implementation is principal-specific. I wanna create similar implementations for Student and Teacher and use them accordingly.
You don't need to create more than one implementation for UserDetailsService. Student, Teacher are also users, only one thing will differ these users is "authorities"(role & authorities) in the application if we look at from general view. Spring Security firstly checks "username" and "password" for authentication and after successful authentication, it checks "authorities" for authorization process in order to allow to use resources(methods, and etc) according to the business logic of the application.

User account is locked when signing in Spring Securty

I followed this guide Spring boot security + JWT in order to learn how to secure an application using spring boot security and jwt and i am calling the /authenticate api to test login functionality.
#PostMapping(value = "/authenticate")
public ResponseEntity<?> createAuthenticationToken(#RequestBody User authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
I am using postman to call the api passing username and password in json :
{
"username":"user",
"password":"pass"}
AuthenticationManager.authenticate is throwing User is Locked
My implementation of UserDetails is directly on a User Entity, not the best practices but i didnt have a better idea for how to do it now, maybe i should have some kind of DTO that implements is and have it as argument to createAuthenticationToken()
These are the overriden methods comming from UserDetails:
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
Any help is appreciated.
isAccountNonLocked
boolean isAccountNonLocked()
Indicates whether the user is locked or unlocked. A locked user cannot be authenticated.
Returns:
true if the user is not locked, false otherwise
false means the user is locked.You should return true from the method for the user to be not locked.

Spring Boot rendering index page after authentication

I have implemented SSO SAML using Spring Security. In my Spring Boot project I have the following controller which basically redirects a user to an idP login page it then generates a JWT token based on a successful login. This JWT token is then forwarded to the index page as a header. But I cant seem to get this to work properly.
Auth Controller,
#Controller
public class AuthController {
private static final Logger log = LoggerFactory.getLogger(UserAccountResource.class);
#Inject
private TokenProvider tokenProvider;
/**
* Given that a user is already authenticated then generate a token
*
* #return the ResponseEntity with status 200 (OK) and with body of the updated {#link JWTToken} jwt token
*/
#RequestMapping(value = "/auth/login")
public String login(HttpServletResponse response) throws Exception {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new UnauthorizedException();
} else {
try {
final SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
final DateTime dateTime = credential.getAuthenticationAssertion()
.getIssueInstant()
.toDateTime(DateTimeZone.forTimeZone(TimeZone.getDefault()));
String jwt = tokenProvider.createToken(authentication, dateTime.getMillis(), false);
response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
log.debug("Generated jwt {}", jwt);
log.debug("SAMLCredential {}", credential);
return "forward:/";
} catch (Exception e) {
throw new UnauthorizedException(e);
}
}
}
}
WebMvcConfigurerAdapter is as follows,
#Configuration("webConfigurer")
public class WebConfigurer extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");
}
}
As far as SSO via SAML goes everything works great. The user gets redirected to the idP login e.t.c. What I cant figure out is why the forward isn't work as expected.
All my UI (Angular 4.x) is initiated with index.html.
When I tested this I can see it being forwarded to / however no headers come through.
What I did in the end is to separate the login and jwt generation to two APIs calls which worked great.
#GetMapping("/login")
public String samlLogin() throws Exception {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new UnauthorizedException("Unable to get the SAML authentication information");
} else {
try {
final SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
if (credential == null) {
throw new UnauthorizedException("Not valid SAML credentials");
}
return "forward:/";
} catch (Exception e) {
throw new UnauthorizedException(e);
}
}
}
#GetMapping("/jwt")
public ResponseEntity<String> generateJWT(HttpServletResponse response) throws Exception {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new UnauthorizedException("Unable to get the SAML authentication information");
} else {
try {
final SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
if (credential == null) {
throw new UnauthorizedException("Not valid SAML credentials");
}
final DateTime dateTime = credential.getAuthenticationAssertion()
.getIssueInstant()
.toDateTime(DateTimeZone.forTimeZone(TimeZone.getDefault()));
String jwt = tokenProvider.createToken(authentication, dateTime.getMillis(), false);
response.addHeader(JWTConfigurer.AUTHORIZATION_HEADER, "Bearer " + jwt);
log.debug("Generated jwt {} for SAML authentication", jwt);
return new ResponseEntity<>(jwt, HttpStatus.OK);
} catch (Exception e) {
throw new UnauthorizedException(e);
}
}
}

OAuth2 userdetails not updating in spring security (SpringBoot)

I am new to spring security and have used jhipster in which i have configured db and LDAP based authentications. Now i have integrated it with OAuth client using #enableOAuthSso. I can able to authenticate using external OAuth Idp (Okta) and it is redirecting to my application and my principle is getting updated and i can access resources through rest. But my userDetails object not getting populated.
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) {
try {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
auth
.ldapAuthentication()
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator)
.userDnPatterns("uid={0},ou=people")
.userDetailsContextMapper(ldapUserDetailsContextMapper)
.contextSource(getLDAPContextSource());
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
I have check by going deep where its getting failed and found out the following
public static String getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
String userName = null;
if (authentication != null) {
log.info("authentication is not null");
if (authentication.getPrincipal() instanceof UserDetails) { //failing here
log.info("principle is instance of userdetails");
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
log.info(springSecurityUser.getUsername());
userName = springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
userName = (String) authentication.getPrincipal();
}
}
return userName;
}
Its failing at the line
if(authentication.getPrincipal() instanceof UserDetails)
What is the possible and the best way to handle this to update user details object.
Update:
#Transactional(readOnly = true)
public User getUserWithAuthorities() {
log.info("======inside getUserWithAuthorities =================");
log.info("current user is :::::::"+SecurityUtils.getCurrentUserLogin());
Optional<User> optionalUser = userRepository.findOneByLogin(SecurityUtils.getCurrentUserLogin());
User user = null;
if (optionalUser.isPresent()) {
user = optionalUser.get();
user.getAuthorities().size(); // eagerly load the association
}
return user;
}
Its trying to fetch the user from db. But the user is not present in the database
Similar to the LDAP tip, I would reocmmend creaing an OktaUserDetails class and casting the principal. Then you can keep most of the authentication code the same. The LDAP code example is below, the format of OktaUserDetails would depend on the JSON response
} else if (authentication.getPrincipal() instanceof LdapUserDetails) {
LdapUserDetails ldapUser = (LdapUserDetails) authentication.getPrincipal();
return ldapUser.getUsername();
}
To save information received from an Oauth2 resource, declare a PrincipalExtractor Bean in your SecurityConfiguration. This lets you parse the response in a custom manner. A basic example is below (source).
#Bean
public PrincipalExtractor principalExtractor(UserRepository userRepository) {
return map -> {
String principalId = (String) map.get("id");
User user = userRepository.findByPrincipalId(principalId);
if (user == null) {
LOGGER.info("No user found, generating profile for {}", principalId);
user = new User();
user.setPrincipalId(principalId);
user.setCreated(LocalDateTime.now());
user.setEmail((String) map.get("email"));
user.setFullName((String) map.get("name"));
user.setPhoto((String) map.get("picture"));
user.setLoginType(UserLoginType.GOOGLE);
user.setLastLogin(LocalDateTime.now());
} else {
user.setLastLogin(LocalDateTime.now());
}
userRepository.save(user);
return user;
};
}

Resources