Override the existing Spring Security authentication - java

How can I override the existing Spring Security authentication by invoking a Web Service and when it's failed, need to redirect some third party login page.
For calling this authentication web service, I need to get some ServletRequest parameter and for redirection, I need to access the ServletResponse.
Therefore I need to find out some Authentication method with ServletRequest and ServletResponse parameters.
But still, I failed to find out such a ProcessingFilter or AuthenticationProvider.
According to Spring Security basic it seems I have to override the AuthenticationProvider related authenticate method.
According to use case, I have to implement the Spring Security Pre-authentication,
but the issue is PreAuthenticatedAuthenticationProvider related 'authenticate' method only having the Authentication parameter.
PreAuthenticatedAuthenticationProvider
public class PreAuthenticatedAuthenticationProvider implements
AuthenticationProvider, InitializingBean, Ordered {
public Authentication authenticate(Authentication authentication) {}
}
As solution, is there any possibility to use custom implementation of AuthenticationFailureHandler ?
Thanks.

I have got resolved the issue as following manner,
Implementing a custom AbstractPreAuthenticatedProcessingFilter
Override the doFilter method
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// Get current Authentication object from SecurityContext
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// Call for third party WS when the Authenticator object is null
if (auth == null) {
logger.debug("doFilter : Proceed the authentication");
String appId = "My_APP_ID";
String redirectURL = request.getRequestURL().toString();
// Call for third party WS for get authenticate
if (WS_Authenticator.isAuthenticated(appId, redirectURL)) {
// Successfully authenticated
logger.debug("doFilter : WS authentication success");
// Get authenticated username
String userName = WS_Authenticator.getUserName();
// Put that username to request
request.setAttribute("userName", userName);
} else {
String redirectURL = WS_Authenticator.getAuthorizedURL();
logger.debug("doFilter : WS authentication failed");
logger.debug("doFilter : WS redirect URL : " + redirectURL);
((HttpServletResponse) response).setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
((HttpServletResponse) response).sendRedirect(redirectURL);
// Return for bypass the filter chain
return;
}
} else {
logger.debug("doFilter : Already authenticated");
}
} catch (Exception e) {
logger.error("doFilter: " + e.getMessage());
}
super.doFilter(request, response, chain);
return;
}
Override the getPreAuthenticatedCredentials method
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
// Get authenticated username
String[] credentials = new String[1];
credentials[0] = (String) request.getAttribute("userName");
return credentials;
}
Implementing a CustomAuthenticationUserDetailsServiceImpl
Override the loadUserDetails method
public class CustomAuthenticationUserDetailsServiceImpl implements AuthenticationUserDetailsService<Authentication> {
protected static final Logger logger = Logger.getLogger(CustomAuthenticationUserDetailsServiceImpl.class);
#Autowired
private UserDataService userDataService;
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
// Get authenticated username
String[] credentials = (String[]) token.getCredentials();
String userName = credentials[0];
try {
// Get user by username
User user = userDataService.getDetailsByUserName(userName);
// Get authorities username
List<String> roles = userDataService.getRolesByUserName(userName);
user.setCustomerAuthorities(roles);
return user;
} catch (Exception e) {
logger.debug("loadUserDetails: User not found! " + e.getMessage());
return null;
}
}
}

Related

While using Jetty as a server, how do I return a clear json 401 error when JWT Filter blocks unauthenticated users in Spring security?

I have my Spring Security bean which is doing well in blocking unauthorised requests, while using Tomcat, the error response is a clean exception with the message but with Jetty, a text/html is returned even in postman as shown below.
And my doFilterInternal JWT Filter is as below.
public class JwtFilter extends OncePerRequestFilter {
#Autowired
private JWTUtility jwtUtility;
#Autowired
private UserServiceImpl userService;
private final ObjectMapper mapper;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String authorization = httpServletRequest.getHeader("Authorization");
String token = null;
String userName = null;
if(null != authorization && authorization.startsWith("Bearer ")) {
token = authorization.substring(7);
userName = jwtUtility.getUsernameFromToken(token);
}
if(null != userName && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails
= userService.loadUserByUsername(userName);
try {
if (jwtUtility.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
= new UsernamePasswordAuthenticationToken(userDetails,
null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(httpServletRequest)
);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
} catch (Exception e){
// System.out.println("Hello world");
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("ACCESS_DENIED", e.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON));
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
With Tomcat as my server, everything works as expected, but I have shifted to Jetty Server, how best can I replace this code below to work the same for Jetty?
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("ACCESS_DENIED", e.getMessage());
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType(String.valueOf(MediaType.APPLICATION_JSON));
mapper.writeValue(httpServletResponse.getWriter(), errorDetails);
return;
My expected response when the user has not sent in their jwt is as follows:-
HTTP 401 Unauthorized
{
"ACCESS_DENIED": "Some error message here",
}
Otherwise, the request should go through therefore the filter should not return anything.
Note: Please feel free to edit this question to make it better as I have more friends trying to solve the same issue.
The response you are seeing is the default Servlet ERROR dispatch handling (see DispatcherType.ERROR)
Use the standard Servlet error-page mappings to specify your own custom mappings for handling errors. A mapping can be based on status codes, throwables, and more (see Servlet descriptor and <error-page> mappings)

How to validate a custom session in spring

I'm new to spring, so just looking for a general direction / advice.
I'm building a small microservice. The service auth is already setup to validate a JWT that is passed to it (issued by a different microservice).
The JWT contains a sessionid claim. I want to be able to get that claim, and validate that the session is still active.
For now that will be by querying the database directly, although in future when we have a dedicated session-service, I will change it to call that service instead.
I can obviously get the claim in each controller that should validate the session (basically all of them), but I am looking for a more global option, like a request interceptor (that only triggers AFTER the JWT is validated, and has access to the validated JWT).
Is there a recommended way to do this kind of thing in spring?
You have several options to do that: using a ControllerAdvice, perhaps AOP, but probably the way to go will be using a custom security filter.
The idea is exemplified in this article and this related SO question.
First, create a filter for processing the appropriate claim. For example:
public class SessionValidationFilter extends GenericFilterBean {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String AUTHORIZATION_BEARER = "Bearer";
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// We will provide our own validation logic from scratch
// If you are using Spring OAuth or something similar
// you can instead use the already authenticated token, something like:
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
// Jwt jwt = (Jwt) authentication.getPrincipal();
// String sessionId = jwt.getClaimAsString("sessionid");
// ...
// Resolve token from request
String jwt = getTokenFromRequest(httpServletRequest);
if (jwt == null) {
// your choice... mine
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// If the token is not valid, raise error
if (!this.validateToken(jwt)) {
throw new BadCredentialsException("Session expired");
}
// Continue filter chain
filterChain.doFilter(servletRequest, servletResponse);
}
// Resolve token from Authorization header
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith(AUTHORIZATION_BEARER)) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
// Validate the JWT token
// We can use the jjwt library, for instance, to process the JWT token claims
private boolean validateToken(String token) {
try {
Claims claims = Jwts.parser()
// .setSigningKey(...)
.parseClaimsJws(token)
.getBody()
;
String sessionId = (String)claims.get("sessionid");
// Process as appropriate to determine whether the session is valid or not
//...
return true;
} catch (Exception e) {
// consider logging the error. Handle as appropriate
}
return false;
}
}
Now, assuming for instance that you are using Java configuration, add the filter to the Spring Security filter chain after the one that actually is performing the authentication:
#Configuration
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
// the rest of your code
#Override
protected void configure(HttpSecurity http) throws Exception {
// the rest of your configuration
// Add the custom filter
// see https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/reference/htmlsingle/#filter-stack
// you can name every provided filter or any specified that is included in the filter chain
http.addFilterAfter(new SessionValidationFilter(), BasicAuthenticationFilter.class);
}
}

Do I need a separate JWT Filter for Multiple Logins?

The User login is working well but I want to add a Customer Module to the project. I know that I need to write a custom UserDetails class to get the customer Username but I want to ask if I need to write another Custom JWT filter for the Customer Login validation. Presently this is the Filter class that I have for User Login. I have added a username and password field to the Customer entity.
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private UserAccountService myUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} 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.myUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority)
.collect(Collectors.joining());
System.out.println("Authorities granted : " + authorities);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
else {
System.out.println("Not Valid Token");
}
}
chain.doFilter(request, response);
}
}
As you can see the Filter is using the custom UserDetails to verify the username . How do I add the Customer userdetails service to the filter ? This is my first multiple login project please be lenient with me.
Differentiate between user and customer while logging. Accordingly, call the different service to get user details. More can be found here.
Spring Security user authentication against customers and employee
How do I add the Customer userdetails service to the filter?: inject it as you did with UserAccountService. If you do this way, you're using 1 filter (and of course, this filter is in 1 SecurityFilterChain), you could basically implement your filter like: trying to validate your user by myUserDetailsService and if it's not successful, continue with myCustomerDetailsService.
For multiple login project. The second way you could do is using 2 SecurityFilterChain. UserJwtFilter for 1 SecurityFilterChain and CustomJwtFilter for 1 SecurityFilterChain for example. People usually do this way for different login mechanisms Basic, OAuth2, SAML2. E.g:
Basic Authentication:
#Configuration
#Order(2)
public class BasicAuthenticationFilterChain extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/logout")
.and()
OAuth2 Authentication:
#Configuration
#Order(3)
public class OAuth2AuthenticationFilterChain extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/oauth")
.and()
In this case when a request with "/login" it'll be directed to BasicAuthenticationFilterChain, and a request with "/oauth" will go to OAuth2AuthenticationFilterChain. About Order: the lower is the higher priority and once the request's processed with a SecurityFilterChain, it won't go to another SecurityFilterChain. You can implement your project this way.
Conclusion: There are a lot of ways you can implement your idea with spring security, it depends on your choice.
it looks to me like you already did.
#Autowired
private UserAccountService myUserDetailsService;
But I would suggest using a Constructor instead of #Autowired. Spring will fill in the constructor parameters just the same. This could be very slim when you use the lombok library as well.
Using a constructor also makes mocking this a bit easier for testing.
Updated as discussed in the comments:
#Log //another lombok thing
#RequiredArgsConstructor
#Component
public class JwtRequestFilter extends Filter{
private final JwtTokenUtil jwtTokenUtil;
private final UserAccountService myUserDetailsService;
private final CustomerAccountService myCustomerDetailsService;
private static final String AUTH_HEADER = "authorization";
#Override
protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
String tokenHeader = ((HttpServletRequest) request).getHeader(AUTH_HEADER);
if(hasValue(tokenHeader) && tokenHeader.toLowerCase().startsWith("bearer ")){
jwtToken = requestTokenHeader.substring(7);
String username;
String jwtToken;
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
if (uSecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
if(isNull(userDetails)){
userDetails = myCustomerDetailsService.loadCustomerByUsername(username);
}
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
var token = createSecurityToken(userDetails);
SecurityContextHolder.getContext().setAuthentication(token);
} else {
throw new RuntimeException("Not a Valid Token.");
}
} else {
log.info("Authorization already present");
}
} catch (IllegalArgumentException e) {
throw new("Unable to get JWT Token",e);
} catch (ExpiredJwtException e) {
throw new("JWT Token has expired",e);
}
} else {
throw new RuntimeException("No valid authorization header found.");
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken createSecurityToken(UserDetails userDetails){
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
log.info("Authorities granted : {}", userDetails.getAuthorities());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
return token;
}
}

How to redirect my previous page after SSO Login in spring security?

How to redirect my previous page after SSO Login in spring security
I used set userReferer as true ,
But not able achieve it. please suggest some sample code or site.
Spring security with IDP we are using
public class LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public LoginSuccessHandler() {
super();
setUseReferer(true);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
/// some code
//set our response to OK status
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
String targetUrl = determineTargetUrl(authentication);
httpServletResponse.sendRedirect(targetUrl);
}
}
Once the user gets authenticated on IDP (Identity provider) side then SP (Service provider) receives assertions or response from IDP. That response would be validated on SP side. Upon response validation, this class OAuthUserLoginSuccessHandler would be called, where you can extract the information from the response provided by IDP and proceed with redirection as per the following code.
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
public class OAuthUserLoginSuccessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
public OAuthUserLoginSuccessHandler() {}
#Override
public void onAuthenticationSuccess(final HttpServletRequest request,
final HttpServletResponse response, final Authentication authentication)
throws IOException, ServletException {
if (authentication.getPrincipal() instanceof UserDetails
|| authentication.getDetails() instanceof UserDetails) {
UserDetails details;
if (authentication.getPrincipal() instanceof UserDetails) {
details = (UserDetails) authentication.getPrincipal();
} else {
details = (UserDetails) authentication.getDetails();
}
String username = details.getUsername();
// get user info from datastore using username
// some code
String redirectUri; // get target uri either from relay state or from datastore
if (null != redirectUri) {
response.sendRedirect(redirectUri);
return;
}
}
super.onAuthenticationSuccess(request, response, authentication);
}
}

Auditable Entity In Microservice Architecture

I'm trying to implement Abstract Auditable Entity in my current Microservice architecture. It is working fine for a single module but I'm confused on how to pass the SecurityContext across multiple modules.
I've already tried by transferring the token as a header from my zuul-service (auth-server) to other core modules and the value is always null.
Also, I tried passing the SecurityContext using feign client but it didn't work for me either.
Cannot get JWT Token from Zuul Header in Spring Boot Microservice Module
Audit Logging in Spring Microservices
Session Management in microservices
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
private static final int FILTER_ORDER = 0;
private static final boolean SHOULD_FILTER = true;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request1, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String header = request1.getHeader(jwtConfig.getHeader());
if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request1, response);
return;
}
/* new token getting code*/
String token = header.replace(jwtConfig.getPrefix(), "");
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
System.out.println(username);
if (username != null) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username,
null, authorities.stream().map(
SimpleGrantedAuthority::new
).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
System.out.println(String.format("%s request to %s", request1.getMethod(), request1.getRequestURL().toString()));
/* return null;*/
request1.setAttribute("header",token);
chain.doFilter(request1, response);
}
}

Categories