SecurityContextHolder authentication object not available to subsequent requests from the client - spring

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;
}
}

Related

How to have access to token in header to pass it to thymeleaf to be able to do ajax call

I use spring boot with spring cloud gateway
I have another app with spring boot and thymeleaf
Spring gateway return a token to my thymeleaf app.
#EnableWebFluxSecurity
#Configuration
public class WebFluxSecurityConfig {
#Autowired
private WebFluxAuthManager authManager;
#Bean
protected SecurityWebFilterChain securityFilterChange(ServerHttpSecurity http) throws Exception {
http.authorizeExchange()
// URL that starts with / or /login/
.pathMatchers("/", "/login", "/js/**", "/images/**", "/css/**", "/h2-console/**").permitAll()
.anyExchange().authenticated().and().formLogin()
.authenticationManager(authManager)
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccesHandler("/findAllCustomers"));
return http.build();
}
}
WebFluxAuthManager class
#Component
public class WebFluxAuthManager implements ReactiveAuthenticationManager {
#Value("${gateway.url}")
private String gatewayUrl;
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
// return is already authenticated
if (authentication.isAuthenticated()) {
return Mono.just(authentication);
}
String username = authentication.getName();
String password = authentication.getCredentials().toString();
LoginRequest loginRequest = new LoginRequest(username, password);
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
//todo modify to use webclient
HttpPost httpPost = new HttpPost(this.gatewayUrl + "/authenticate");
httpPost.setHeader("Content-type", "application/json");
String jsonReq = converObjectToJson(loginRequest);
StringEntity requestEntity = new StringEntity(jsonReq);
httpPost.setEntity(requestEntity);
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.OK.value()) {
HttpEntity entity = httpResponse.getEntity();
Header encodingHeader = entity.getContentEncoding();
Charset encoding = encodingHeader == null ? StandardCharsets.UTF_8
: Charsets.toCharset(encodingHeader.getValue());
// use org.apache.http.util.EntityUtils to read json as string
String jsonRes = EntityUtils.toString(entity, encoding);
LoginResponse loginResponse = converJsonToResponse(jsonRes);
Collection<? extends GrantedAuthority> authorities = loginResponse.getRoles().stream()
.map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
return Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
} else {
throw new BadCredentialsException("Authentication Failed!!!");
}
} catch (RestClientException | ParseException | IOException e) {
throw new BadCredentialsException("Authentication Failed!!!", e);
} finally {
try {
if (httpClient != null)
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
In WebFluxAuthManager, I have access to the token, now I search a way to transfert it to a fragment.

I have implemented JWT token security in spring boot code. how will I get jwt token anywhere in my code? need to save audit

I have implemented jwt security token in spring boot by refering jwt security impemented videos.
So after login I get generated jwt token, For further end points to hit I need to pass jwt token from request header then re request will get authorize at dofilter() method in JwtAuthenticationTokenFilter class as shown below.
public class JwtAuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Value("${jwt.header}")
private String tokenHeader;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String username = null;
String authToken = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String header = httpRequest.getHeader(this.tokenHeader);
if (header != null && header.startsWith("Bearer ")) {
authToken = header.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
But I need to get that jwt token anywhere i want in my code to get some data from token.
for example look below code
public static AuditDetails createAudit() {
AuditDetails auditDetails = new AuditDetails();
**auditDetails.setCreateUser(token.getUsername());**
auditDetails.setCreateTime(new Date());
return auditDetails;
}
so basically i need to get username from token to same audit details, but how am i suppose get token in that code or anywhere in the code?
The token is sent to your app via the header (tokenHeader)
Edit
If you do not want to use the content of your HttpServletRequest anywhere, you can use as per session, a value holder that you can Inject (autowire) in every service to utilize the submitted token. You can try the following
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyHolder {
private String authToken;
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
}
Change the token value in your JwtAuthenticationTokenFilter
#Autowired MyHolder myHolder;
// ...
String authToken = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String header = httpRequest.getHeader(this.tokenHeader);
if (header != null && header.startsWith("Bearer ")) {
authToken = header.substring(7); // Here is your token
// UPDATE THE TOKEN VALUE IN YOUR HOLDER HERE
myHolder.setAuthToken(authToken);
// ...
}
Access the token anywhere in your app by autowiring the MyHolder class
#Autowired MyHolder myHolder;
// ...
var token = myHolder.getAuthToken();

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;
};
}

How to know that a session is expired?

I set values to the session object in the method of a controller after success of login :
#RequestMapping(value = "/", method = RequestMethod.POST)
public ModelAndView processLogin(Model model, HttpServletRequest request, HttpSession session, #RequestParam String login, #RequestParam String pwd) {
if ( utilisateurDao.verifierLoginUser(login) ) {
if ( utilisateurDao.verifierUser(login, pwd) ) {
HashMap<String, String> criteres = new HashMap<String, String>();
criteres.put("user_login", login);
criteres.put("user_passwd", pwd);
List<Utilisateur> users = utilisateurDao.lireParCritere(criteres);
session.setAttribute("user_code", ((Utilisateur)users.get(0)).getUser_code());
session.setAttribute("menu", menuDao.afficherMenuParUtilisateur((Integer)session.getAttribute("user_code"), env, request, session));
criteres.clear();
users.clear();
criteres.put("user_code", String.valueOf(session.getAttribute("user_code")));
users = utilisateurDao.lireParCritere(criteres);
session.setAttribute("user_names", ((Utilisateur)users.get(0)).getNoms());
session.setAttribute("logout_menu", env.getProperty("menu.logout"));
return new ModelAndView("redirect:/accueil");
} else {
ModelAndView modelViewLogin = new ModelAndView("redirect:/");
modelViewLogin.addObject("e", "p").addObject("l", login);
return modelViewLogin;
}
} else {
ModelAndView modelViewLogin = new ModelAndView("redirect:/");
modelViewLogin.addObject("e", "l");
return modelViewLogin;
}
}
Then I opened the app inactive for some minutes. After that I went to the "accueil" path. Then the menu was not shown anymore ! The menu was got from session. So how to know that the session is expired and where is the convenient place to test it ?
By default in spring security session is stored in SessionRegistry.
By using SecurityContext you can get this info in your controller code.
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
If you want to be notified when session has expired or person logged out you can always register listener on SessionDestroyedEvent- documentation.
example:
#Component
public class LogoutListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
//do your stuff here
}
}
Its also worth to refer to spring docs for that subject.
You can make a Interceptor,
#Component
public class RequestInterceptor extends HandlerInterceptorAdapter
In this interceptor you can control the HttpServletRequest
and check if obj exists into them and then you can throw to a new SessionExpiredException and catch with #ExceptionMapper (https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc)
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if (request.getSession().getAttribute("user")==null) {
throw new SessionExpiredException();
}
return true;
}
I check like below. I think it might be help.
public boolean isUserLoggedIn(HttpServletRequest request) throws IOException {
SecurityContext securityContext = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
if(securityContext != null) {
Authentication authentication = securityContext.getAuthentication();
if(null != authentication && authentication.isAuthenticated() != true)
return false;
else
return true;
} else {
return false;
}
}

Resources