Spring Boot rendering index page after authentication - spring

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

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

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();

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 Security 5 Stateless OAuth2 Login - how to implement cookies based AuthorizationRequestRepository

I'm trying to have Google/Facebook login using Spring Security 5 OAuth2 login feature. But the problem I'm facing is that I'm coding a stateless API, whereas Spring security 5 uses HttpSessionOAuth2AuthorizationRequestRepository to store authorization requests, which uses session. So, I thought not to use that and code a cookie based implementation, which looks as below:
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
private static final String COOKIE_NAME = "some-name";
#Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null");
return fetchCookie(request)
.map(this::toOAuth2AuthorizationRequest)
.orElse(null);
}
#Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request,
HttpServletResponse response) {
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
if (authorizationRequest == null) {
deleteCookie(request, response);
return;
}
Cookie cookie = new Cookie(COOKIE_NAME, fromAuthorizationRequest(authorizationRequest));
cookie.setPath("/");
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
private String fromAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest) {
return Base64.getUrlEncoder().encodeToString(
SerializationUtils.serialize(authorizationRequest));
}
private void deleteCookie(HttpServletRequest request, HttpServletResponse response) {
fetchCookie(request).ifPresent(cookie -> {
cookie.setValue("");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
});
}
#Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
// Question: How to remove the cookie, because we don't have access to response object here.
return loadAuthorizationRequest(request);
}
private Optional<Cookie> fetchCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0)
for (int i = 0; i < cookies.length; i++)
if (cookies[i].getName().equals(COOKIE_NAME))
return Optional.of(cookies[i]);
return Optional.empty();
}
private OAuth2AuthorizationRequest toOAuth2AuthorizationRequest(Cookie cookie) {
return SerializationUtils.deserialize(
Base64.getUrlDecoder().decode(cookie.getValue()));
}
}
The above basically stores the data in a cookie instead of session. I've a couple of questions:
How exactly to code the removeAuthorizationRequest method above? I wanted the cookie removed there, but we don't have access to the response object.
Does the above (cookie based) approach look okay? E.g. any security issues?
Update: Have created an issue at https://github.com/spring-projects/spring-security/issues/5313 . Until that's addressed, here is a workaround I came up with: https://www.naturalprogrammer.com/blog/1681261/spring-security-5-oauth2-login-signup-stateless-restful-web-services

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