-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extract out environment verification logic and add optional Spring Security component #8
Changes from 5 commits
e45a463
5f1593e
3f4b62c
9a38566
5b74c79
5f68663
3e2a90b
8a6a0e8
70afbdb
c24e963
f570478
f7fd8c9
b73c857
810a249
bee312a
8231c25
42664c8
917fd69
f146d7b
830a10c
6b10ba9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package org.esbtools.auth.spring; | ||
|
||
import org.springframework.security.core.AuthenticationException; | ||
|
||
public class CertEnvironmentAuthenticationException extends AuthenticationException { | ||
|
||
private static final long serialVersionUID = -1102864286332227011L; | ||
|
||
public CertEnvironmentAuthenticationException(String msg) { | ||
super(msg); | ||
} | ||
|
||
public CertEnvironmentAuthenticationException(String msg, Throwable t) { | ||
super(msg, t); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.esbtools.auth.spring; | ||
|
||
import java.io.IOException; | ||
import java.security.cert.X509Certificate; | ||
|
||
import javax.naming.NamingException; | ||
import javax.servlet.FilterChain; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
|
||
import org.esbtools.auth.util.Environment; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.WebAttributes; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
public class CertEnvironmentVerificationFilter extends OncePerRequestFilter { | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(CertEnvironmentVerificationFilter.class); | ||
|
||
private final Environment envUtils; | ||
|
||
public CertEnvironmentVerificationFilter(String environment) { | ||
envUtils = (null == environment) ? null : new Environment(environment); | ||
|
||
LOGGER.info("Cert Environment: " + ((environment == null) ? "Not Set" : environment)); | ||
} | ||
|
||
@Override | ||
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
if (null != envUtils) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You also want to check if what is configured is not an empty string (i.e. ""). In the past an empty environment signified you could skip environment validation. I think that the way this is currently written, an empty environment config would still be validated (because it is not null). We could also choose to interpret null for the environment configuration as "doesn't need to be validated", we just need to be consistent and allow for that in CertLoginLdapLoginModule (currently requires environment to be present in config) and whatever else uses this. We could instead a method to the Environment class called requiresValidation() that returns false when the value is null or empty string, and true otherwise (I think I like this option the best). We could also check the allAccessOu value in that method too since that is another factor in determining whether or not to do validation logic. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Do we need to handle blank spaces also? eg. " "
That is exactly what CertEnvironmentVerificationFilter does, it just handles it at that layer instead of in Environment. I did it at that level because I didn't have enough context to make changes to the logic flow of CertLdapLoginModule. If we want this to be default behaviour, then it can definitely be moved to the Environment / requiresValidation. |
||
LOGGER.debug("Attempting Environment Cert verification"); | ||
X509Certificate certChain[] = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); | ||
|
||
if ((null != certChain) && (certChain.length > 0)) { | ||
LOGGER.debug("Verifying environment on cert"); | ||
String dn = certChain[0].getSubjectDN().getName(); | ||
try { | ||
envUtils.validate(dn); | ||
} | ||
catch (NamingException e) { | ||
unsuccessfulAuthentication(request, response, | ||
new CertEnvironmentAuthenticationException(e.getMessage())); | ||
return; //end the chain | ||
} | ||
} | ||
else { | ||
LOGGER.debug("Cert not found. Skipping Environment Cert verification."); | ||
} | ||
} | ||
else { | ||
LOGGER.debug("No environment configured. Skipping Environment Cert verification."); | ||
} | ||
|
||
chain.doFilter(request, response); | ||
} | ||
|
||
/** | ||
* Ensures the authentication object in the secure context is set to null when | ||
* authentication fails. | ||
* <p> | ||
* Caches the failure exception as a request attribute | ||
*/ | ||
protected void unsuccessfulAuthentication(HttpServletRequest request, | ||
HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { | ||
SecurityContextHolder.clearContext(); | ||
|
||
if (LOGGER.isDebugEnabled()) { | ||
LOGGER.debug("Cleared security context due to exception", failed); | ||
} | ||
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, failed); | ||
|
||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, failed.getMessage()); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package org.esbtools.auth.spring; | ||
|
||
import java.util.stream.Collectors; | ||
|
||
import org.esbtools.auth.ldap.LdapConfiguration; | ||
import org.esbtools.auth.ldap.LdapRolesProvider; | ||
import org.esbtools.auth.util.CachedRolesProvider; | ||
import org.esbtools.auth.util.RolesCache; | ||
import org.esbtools.auth.util.RolesProvider; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; | ||
import org.springframework.security.core.userdetails.User; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.security.core.userdetails.UsernameNotFoundException; | ||
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; | ||
|
||
public class LdapUserDetailsService implements UserDetailsService, AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> { | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(LdapUserDetailsService.class); | ||
|
||
private final RolesProvider rolesProvider; | ||
|
||
public LdapUserDetailsService(String searchBase, LdapConfiguration ldapConfiguration, int rolesCacheExpiryMS) throws Exception { | ||
this(new LdapRolesProvider(searchBase, ldapConfiguration), rolesCacheExpiryMS); | ||
} | ||
|
||
public LdapUserDetailsService(String searchBase, LdapConfiguration ldapConfiguration) throws Exception { | ||
this(new LdapRolesProvider(searchBase, ldapConfiguration)); | ||
} | ||
|
||
public LdapUserDetailsService(LdapRolesProvider rolesProvider, int rolesCacheExpiryMS) { | ||
this(new CachedRolesProvider(rolesProvider, new RolesCache(rolesCacheExpiryMS))); | ||
} | ||
|
||
public LdapUserDetailsService(RolesProvider rolesProvider) { | ||
this.rolesProvider = rolesProvider; | ||
} | ||
|
||
@Override | ||
public UserDetails loadUserDetails(PreAuthenticatedAuthenticationToken token) throws UsernameNotFoundException { | ||
return loadUserByUsername(token.getName()); | ||
} | ||
|
||
@Override | ||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | ||
LOGGER.debug("Using LdapUserDetailsService with principal: " + username); | ||
|
||
try { | ||
return new User( | ||
username, | ||
"no-password", | ||
rolesProvider.getUserRoles(username).stream() | ||
.map(n -> new SimpleGrantedAuthority(n)) | ||
.collect(Collectors.toList()) | ||
); | ||
} | ||
catch (Exception e) { | ||
LOGGER.error("Unable to check ldap for unknown reason.", e); | ||
|
||
throw new UsernameNotFoundException(username + " could not be authorized"); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably want to rename variable too to remove the Utils from the name