Related
I recently upgraded to Spring Security 6, and have found that authenticating using basic auth from JS or from curl no longer works but authenticating with basic auth using Java's HttpClient does work. My goal is to be able to authenticate with all approaches.
The app uses Java 17, Spring Security 6, and Spring Session 3. It has a "login" endpoint which is just a convenience endpoint that is expected to be hit with basic auth and create a session, and it returns a User object. The session id should be used for subsequent requests to other endpoints.
The curl command is like so:
curl -kv --user admin:admin "https://localhost:9000/login"
VS the HttpClient is configured like so and calling HttpClient.get(loginUrl)
HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(300))
.cookieHandler(new CookieManager())
.authenticator(new BasicAuthenticator(username, password))
.sslContext(createSsl())
.build();
public class BasicAuthenticator extends Authenticator {
private PasswordAuthentication authentication;
public BasicAuthenticator(String username, String password) {
authentication = new PasswordAuthentication(username, password.toCharArray());
}
#Override
public PasswordAuthentication getPasswordAuthentication() {
return authentication;
}
}
The security configuration is the block below... In upgrading to SpringSecurity 6 I added the requireExplicitSave() method, I have suspicions around this because my trouble is around saving sessions, but the added code is supposed to have spring security using the old functionality.
http
.securityContext( securityContext -> securityContext.requireExplicitSave(false))
.authorizeHttpRequests((authz) -> authz
.requestMatchers(openEndpoints).permitAll()
.anyRequest().authenticated()
)
.httpBasic()
.and()
.csrf()
.disable()
.exceptionHandling()
.accessDeniedHandler((req, resp, e) -> e.printStackTrace() )
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true);
I turned on request logging, security logging, and SQL logging. The SQL is all the same, and the basic auth request is always authenticated for all scenarios. The headers are different, but I can't see the headers for the HttpClient preflight call, and of the headers I do see, I don't know why authentication or session creation would work for one set of headers but not the other.
The core of the problem seems to be that the login request from the HttpClient ends with a session being created and the request from curl does not. Note that the big difference in the server logs when using curl is "Failed to create a session, as response has been committed. Unable to store SecurityContext." However even stepping through the spring security code I can't tell what is causing the difference.
See logs here:
CURL
2022-12-14T16:38:07.594-05:00 DEBUG 92726 --- [nio-9000-exec-1] o.s.security.web.FilterChainProxy : Securing GET /login
2022-12-14T16:38:07.597-05:00 DEBUG 92726 --- [nio-9000-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-12-14T16:38:07.704-05:00 DEBUG 92726 --- [nio-9000-exec-1] org.hibernate.SQL : select u1_0.id,u1_0.display_name,u1_0.email,u1_0.enabled,u1_0.password,u1_0.registration_time,r1_0.user_id,r1_0.role_id,u1_0.username from app_user u1_0 join user_role r1_0 on u1_0.id=r1_0.user_id where u1_0.username=?
2022-12-14T16:38:07.797-05:00 DEBUG 92726 --- [nio-9000-exec-1] o.s.s.a.dao.DaoAuthenticationProvider : Authenticated user
2022-12-14T16:38:07.799-05:00 DEBUG 92726 --- [nio-9000-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN, ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=null], Granted Authorities=[ROLE_ADMIN, ROLE_USER]]
2022-12-14T16:38:07.801-05:00 DEBUG 92726 --- [nio-9000-exec-1] o.s.security.web.FilterChainProxy : Secured GET /login
2022-12-14T16:38:07.805-05:00 DEBUG 92726 --- [nio-9000-exec-1] o.s.w.f.CommonsRequestLoggingFilter : Before request [GET /login, headers=[host:"localhost:9000", authorization:"Basic YWRtaW46YWRtaW4=", user-agent:"curl/7.84.0", accept:"*/*"]]
2022-12-14T16:38:07.816-05:00 DEBUG 92726 --- [nio-9000-exec-1] horizationManagerBeforeMethodInterceptor : Authorizing method invocation ReflectiveMethodInvocation: public com.seebie.dto.User com.seebie.server.controller.UserController.login(java.security.Principal); target is of class [com.seebie.server.controller.UserController]
2022-12-14T16:38:07.822-05:00 DEBUG 92726 --- [nio-9000-exec-1] horizationManagerBeforeMethodInterceptor : Authorized method invocation ReflectiveMethodInvocation: public com.seebie.dto.User com.seebie.server.controller.UserController.login(java.security.Principal); target is of class [com.seebie.server.controller.UserController]
2022-12-14T16:38:07.826-05:00 DEBUG 92726 --- [nio-9000-exec-1] org.hibernate.SQL : select u1_0.id,u1_0.display_name,u1_0.email,u1_0.enabled,u1_0.password,u1_0.registration_time,u1_0.username from app_user u1_0 where u1_0.username=?
2022-12-14T16:38:07.832-05:00 DEBUG 92726 --- [nio-9000-exec-1] org.hibernate.SQL : select r1_0.user_id,r1_0.role_id from user_role r1_0 where r1_0.user_id=?
2022-12-14T16:38:07.836-05:00 DEBUG 92726 --- [nio-9000-exec-1] org.hibernate.SQL : select a1_0.user_id,a1_0.id,a1_0.city,a1_0.line1,a1_0.state,a1_0.zip from address a1_0 where a1_0.user_id=?
2022-12-14T16:38:07.840-05:00 DEBUG 92726 --- [nio-9000-exec-1] org.hibernate.SQL : select s1_0.principal_name,s1_0.primary_id,s1_0.session_id from spring_session s1_0 where s1_0.principal_name=?
2022-12-14T16:38:07.871-05:00 DEBUG 92726 --- [nio-9000-exec-1] o.s.w.f.CommonsRequestLoggingFilter : REQUEST DATA : GET /login, headers=[host:"localhost:9000", authorization:"Basic YWRtaW46YWRtaW4=", user-agent:"curl/7.84.0", accept:"*/*"]]
2022-12-14T16:38:07.873-05:00 WARN 92726 --- [nio-9000-exec-1] w.c.HttpSessionSecurityContextRepository : Failed to create a session, as response has been committed. Unable to store SecurityContext.
2022-12-14T16:38:07.873-05:00 DEBUG 92726 --- [nio-9000-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
HttpClient
2022-12-14T06:31:28.390-05:00 DEBUG 85610 --- [o-auto-1-exec-1] o.s.security.web.FilterChainProxy : Securing GET /login
2022-12-14T06:31:28.420-05:00 DEBUG 85610 --- [o-auto-1-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-12-14T06:31:28.913-05:00 DEBUG 85610 --- [o-auto-1-exec-1] org.hibernate.SQL : select u1_0.id,u1_0.display_name,u1_0.email,u1_0.enabled,u1_0.password,u1_0.registration_time,r1_0.user_id,r1_0.role_id,u1_0.username from app_user u1_0 join user_role r1_0 on u1_0.id=r1_0.user_id where u1_0.username=?
2022-12-14T06:31:29.102-05:00 DEBUG 85610 --- [o-auto-1-exec-1] o.s.s.a.dao.DaoAuthenticationProvider : Authenticated user
2022-12-14T06:31:29.103-05:00 DEBUG 85610 --- [o-auto-1-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN, ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=19ab0971-5fb3-47fd-a4f9-cdde1ad24883], Granted Authorities=[ROLE_ADMIN, ROLE_USER]]
2022-12-14T06:31:29.108-05:00 DEBUG 85610 --- [o-auto-1-exec-1] o.s.security.web.FilterChainProxy : Secured GET /login
2022-12-14T06:31:29.136-05:00 DEBUG 85610 --- [o-auto-1-exec-1] o.s.w.f.CommonsRequestLoggingFilter : Before request [GET /login, headers=[authorization:"Basic YWRtaW46YWRtaW4=", content-length:"0", host:"localhost:64723", user-agent:"Java-http-client/17.0.2", cookie:"SESSION=MTlhYjA5NzEtNWZiMy00N2ZkLWE0ZjktY2RkZTFhZDI0ODgz", Content-Type:"application/json;charset=UTF-8"]]
2022-12-14T06:31:29.274-05:00 DEBUG 85610 --- [o-auto-1-exec-1] horizationManagerBeforeMethodInterceptor : Authorizing method invocation ReflectiveMethodInvocation: public com.seebie.dto.User com.seebie.server.controller.UserController.login(java.security.Principal); target is of class [com.seebie.server.controller.UserController]
2022-12-14T06:31:29.332-05:00 DEBUG 85610 --- [o-auto-1-exec-1] horizationManagerBeforeMethodInterceptor : Authorized method invocation ReflectiveMethodInvocation: public com.seebie.dto.User com.seebie.server.controller.UserController.login(java.security.Principal); target is of class [com.seebie.server.controller.UserController]
2022-12-14T06:31:29.373-05:00 DEBUG 85610 --- [o-auto-1-exec-1] org.hibernate.SQL : select u1_0.id,u1_0.display_name,u1_0.email,u1_0.enabled,u1_0.password,u1_0.registration_time,u1_0.username from app_user u1_0 where u1_0.username=?
2022-12-14T06:31:29.392-05:00 DEBUG 85610 --- [o-auto-1-exec-1] org.hibernate.SQL : select r1_0.user_id,r1_0.role_id from user_role r1_0 where r1_0.user_id=?
2022-12-14T06:31:29.409-05:00 DEBUG 85610 --- [o-auto-1-exec-1] org.hibernate.SQL : select a1_0.user_id,a1_0.id,a1_0.city,a1_0.line1,a1_0.state,a1_0.zip from address a1_0 where a1_0.user_id=?
2022-12-14T06:31:29.413-05:00 DEBUG 85610 --- [o-auto-1-exec-1] org.hibernate.SQL : select s1_0.principal_name,s1_0.primary_id,s1_0.session_id from spring_session s1_0 where s1_0.principal_name=?
2022-12-14T06:31:29.678-05:00 DEBUG 85610 --- [o-auto-1-exec-1] o.s.w.f.CommonsRequestLoggingFilter : REQUEST DATA : GET /login, headers=[authorization:"Basic YWRtaW46YWRtaW4=", content-length:"0", host:"localhost:64723", user-agent:"Java-http-client/17.0.2", cookie:"SESSION=MTlhYjA5NzEtNWZiMy00N2ZkLWE0ZjktY2RkZTFhZDI0ODgz", Content-Type:"application/json;charset=UTF-8"]]
2022-12-14T06:31:29.680-05:00 DEBUG 85610 --- [o-auto-1-exec-1] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN, ROLE_USER]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=127.0.0.1, SessionId=19ab0971-5fb3-47fd-a4f9-cdde1ad24883], Granted Authorities=[ROLE_ADMIN, ROLE_USER]]] to HttpSession [org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper$HttpSessionWrapper#7ba784f2]
2022-12-14T06:31:29.680-05:00 DEBUG 85610 --- [o-auto-1-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
Note the session in headers for the HttpClient call... I think HttpClient makes a preflight auth call that gets a 401 and then makes the "real" call with the credentials, at least that's how it was in Java 11.
I think if I understood the difference in how these calls are being made/handled that causes one technique to work but not the other, I would be able to solve the problem. So that really is the question: What is the difference in spring security 6 (along with spring session) handling session creation when using Java 17's HttpClient vs curl?
[UPDATE] to anyone who read this far: the behavior is actually expected behavior for Spring Security. A full discussion and explanation are in the spring security issue that I had opened here
Well, if we are not going to investigate why preflight requests make sense (IMO, that seems to be a bug), the explanation of what has been changed in spring 6 is following:
as was mentioned in Session Management Migrations now Spring does not enable SecurityContextPersistenceFilter by default, however in Spring 5 SecurityContextPersistenceFilter was responsible for saving SecurityContext in http session (and hence creating it) unless that was explicitly disabled. Now in order to return previous behaviour you desire you need to setup SecurityContextRepository via:
http.securityContext(securityContext -> securityContext.
securityContextRepository(new HttpSessionSecurityContextRepository())
)
I have a login page in my application where I want to validate the entered username/password against Ldap AD. I am thinking of creating a bind and get a context. If bind is successful that means user is authenticated. In Java I have achieved it like this:
public class Test {
public static void main(String[] args) {
try {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://ldapserver:389");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "domain\\userId"); //coming from frontend login form
env.put(Context.SECURITY_CREDENTIALS, password); // from login form
LdapContext ctx = new InitialLdapContext(env, null);
ctx.setRequestControls(null);
NamingEnumeration<?> namingEnum = ctx.search("ou=users,ou=in,dc=global,dc=company,dc=org", "(objectclass=user)", getSimpleSearchControls());
for (int i=0;i<1;i++) {
SearchResult result = (SearchResult) namingEnum.next();
Attributes attrs = result.getAttributes();
NamingEnumeration<String> nam =attrs.getIDs();
while(nam.hasMore()) {
System.out.println(nam.next());
}
}
namingEnum.close();
} catch (Exception e) {
e.printStackTrace();
}
}
private static SearchControls getSimpleSearchControls() {
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setTimeLimit(30000);
//String[] attrIDs = {"objectGUID"};
//searchControls.setReturningAttributes(attrIDs);
return searchControls;
}
}
Above code is perfectly working for me. I want to implement the same in spring boot application using spring security. I have tried multiple suggested ways but getting some error every time.
Here I want to authenticate the enter username/password for which I think context binding is enough so not require to search that user again using something like "sAMAccountName={0}" please correct me if I am wrong.
Update
while trying below code, I can see in the logs that it fetches the user details but in the last giving some error:
auth
.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.userDnPatterns()
.userSearchBase("dc=global,dc=company,dc=org")
.contextSource()
.url("ldap://ldapserver")
.port(389)
.managerDn("domain\\userId")
.managerPassword("******");
Error Logs:
2022-08-04 19:47:44.477 TRACE 33541 --- [nio-8080-exec-2] o.s.s.w.a.www.BasicAuthenticationFilter : Found username 'userId' in Basic Authorization header
2022-08-04 19:47:44.478 TRACE 33541 --- [nio-8080-exec-2] o.s.s.authentication.ProviderManager : Authenticating request with LdapAuthenticationProvider (1/1)
2022-08-04 19:47:44.478 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.a.LdapAuthenticationProvider : Processing authentication request for user: userId
2022-08-04 19:47:44.479 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.s.FilterBasedLdapUserSearch : Searching for user 'userId', with user search [ searchFilter: '(sAMAccountName={0})', searchBase: 'dc=global,dc=company,dc=org', scope: subtree, searchTimeLimit: 0, derefLinkFlag: false ]
2022-08-04 19:47:44.831 DEBUG 33541 --- [nio-8080-exec-2] o.s.l.c.support.AbstractContextSource : Got Ldap context on server 'ldap://ldapserver'
2022-08-04 19:47:45.241 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate : Searching for entry under DN '', base = 'dc=global,dc=company,dc=org', filter = '(sAMAccountName={0})'
2022-08-04 19:47:45.252 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate : Found DN: CN=Kumar\, Rajesh,OU=Users,OU=IN,DC=global,DC=company,DC=org
2022-08-04 19:47:45.253 INFO 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate : Ignoring PartialResultException
2022-08-04 19:47:45.254 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.a.BindAuthenticator : Attempting to bind as cn=Kumar\, Rajesh,ou=Users,ou=IN,dc=global,dc=company,dc=org
2022-08-04 19:47:45.254 DEBUG 33541 --- [nio-8080-exec-2] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user cn=Kumar\, Rajesh,ou=Users,ou=IN,dc=global,dc=company,dc=org
2022-08-04 19:47:45.622 DEBUG 33541 --- [nio-8080-exec-2] o.s.l.c.support.AbstractContextSource : Got Ldap context on server 'ldap://ldapserver'
2022-08-04 19:47:45.623 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.l.a.BindAuthenticator : Retrieving attributes... 2022-08-04 19:47:45.625 DEBUG 33541 --- [nio-8080-exec-2] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Getting authorities for user cn=Kumar\, Rajesh,ou=Users,ou=IN,dc=global,dc=company,dc=org
2022-08-04 19:47:45.627 DEBUG 33541 --- [nio-8080-exec-2] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Searching for roles for user 'userId', DN = 'cn=Kumar\, Rajesh,ou=Users,ou=IN,dc=global,dc= company,dc=org', with filter (uniqueMember={0}) in search base ''
2022-08-04 19:47:45.628 DEBUG 33541 --- [nio-8080-exec-2] o.s.s.ldap.SpringSecurityLdapTemplate : Using filter: (uniqueMember=cn=Kumar\5c, Rajesh,ou=Users,ou=IN,dc=global,dc= company,dc=org)
2022-08-04 19:47:45.628 DEBUG 33541 --- [nio-8080-exec-2] o.s.ldap.core.LdapTemplate : The returnObjFlag of supplied SearchControls is not set but a ContextMapper is used - setting flag to true
2022-08-04 19:47:45.629 DEBUG 33541 --- [nio-8080-exec-2] o.s.l.c.support.AbstractContextSource : Got Ldap context on server 'ldap://ldapserver'
2022-08-04 19:47:45.837 TRACE 33541 --- [nio-8080-exec-2] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
2022-08-04 19:47:45.838 DEBUG 33541 --- [nio-8080-exec-2] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-08-04 19:47:45.838 DEBUG 33541 --- [nio-8080-exec-2] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
2022-08-04 19:47:45.839 TRACE 33541 --- [nio-8080-exec-2] o.s.b.w.s.f.OrderedRequestContextFilter : Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade#1c138f13
2022-08-04 19:47:45.847 ERROR 33541 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100217, problem 2001 (NO_OBJECT), data 0, best match of: '' ];
nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100217, problem 2001 (NO_OBJECT), data 0, best match of: '' ]; remaining name ''
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:183) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:376) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:328) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:629) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:570) ~[spring-ldap-core-2.3.4.RELEASE.jar:2.3.4.RELEASE]
Can someone please guide me to migrate this plain java code to spring security.
Regards
We can see in the trace logs the following message :
DefaultLdapAuthoritiesPopulator : Searching for roles for user 'userId', DN = 'cn=Kumar\, Rajesh,ou=Users,ou=IN,dc=global,dc= company,dc=org', with filter (uniqueMember={0}) in search base ''
The documentation says :
After authenticating the user successfully, the
LdapAuthenticationProvider will attempt to load a set of authorities
for the user by calling the configured LdapAuthoritiesPopulator.
The default implementation is trying to load the authorities by searching the directory for groups the user is a member of, but is failing to do so because you did not specify the group search base.
auth
.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.userSearchBase("dc=global,dc=company,dc=org")
.groupSearchBase("dc=global,dc=company,dc=org") // <- here
.contextSource()
.url("ldap://ldapserver")
.port(389)
.managerDn("domain\\userId")
.managerPassword("******");
Usually, such groups are referenced under an ou=Roles component in the directory tree. For example, given the user search base defined in the "working" code : ou=roles,ou=in,dc=global,dc=company,dc=org, but a larger base (with only dc's) should be fine to start with.
It is worth noting that you can set a global base directly in the ldap url, and define relative dn for parameters where a dn is expected (with a valid global base set, leaving groupSearchBase empty wouldn't throw an error), you could have for example :
.userSearchBase("ou=users,ou=in")
.groupSearchBase("ou=roles,ou=in")
with :
.url("ldap://ldapserver:389/dc=global,dc=company,dc=org")
Also, note that you don't need userDnPatterns() when using userSearchFilter(), use either one or the other.
#see Spring Security documentation : Loading Authorities
I followed the article, https://learn.microsoft.com/en-us/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory and created an App role
with hasAuthority, I could validate a single role. However, I want to allow the user who is either an Admin or has Contributor permission. I tried with hasRole annotation as suggested in Multiple roles using #PreAuthorize.
// #PreAuthorize("hasAuthority('APPROLE_Admin')")
#PreAuthorize("hasRole('Admin')")
#GetMapping("/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorials(#RequestParam(required = false) String title) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
But it throws the following error
Update: 24/05/2022
This code is working
#PreAuthorize("hasAnyAuthority('APPROLE_Admin', 'APPROLE_Contributor')")
But the below code is throwing an error
//#PreAuthorize("hasRole('ROLE_Admin')")
//#PreAuthorize("hasAnyRole('Admin', 'Contributor')")
//#PreAuthorize("hasAnyRole('ROLE_Admin', 'ROLE_Contributor')")
Logs:
[nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : GET "/api/tutorials", parameters={}
[nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.bezkoder.spring.mssql.controller.TutorialController#getAllTutorials(String)
[nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.security.access.AccessDeniedException: Access is denied
[nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
[nio-8080-exec-5] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
[nio-8080-exec-5] o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
[nio-8080-exec-5] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 403
I'm trying to set up ldap authentication using spring boot. I've done this on another project a while back setting up the authorization through the tomcat context file. So I already have a working setup to reference, just not in spring boot format.
In my SecurityConfiguration I add
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase("OU=users,DC=dom,DC=company,DC=com")
.userSearchFilter("(sAMAccountName={0})")
.contextSource()
.url("ldaps://ldap.company.com:424")
.managerDn("CN=managerUser,OU=services,DC=dom,DC=company,DC=com")
.managerPassword("password");
}
Now when the app starts, I go to a secure resource, enter basic auth credentials, I get the print out in the console window that looks like everything is going fine. It finds the user. Then it finishes with this error.
2018-03-06 17:40:22.632 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'user'
2018-03-06 17:40:22.632 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.ldap.authentication.Lda
pAuthenticationProvider
2018-03-06 17:40:22.632 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.l.a.LdapAuthenticationProvider : Processing authentication request for user: user
2018-03-06 17:40:22.632 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.l.s.FilterBasedLdapUserSearch : Searching for user 'user', with user search [ searchFilter: '(sAMAccountName={0})', searchBase: 'DC=dom,DC=company,DC=com', scope: subtree, searchTimeLimit: 0, derefLinkFlag: false ]
2018-03-06 17:40:22.671 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.ldap.SpringSecurityLdapTemplate : Searching for entry under DN '', base = 'DC=dom,DC=company,DC=com', filter = '(sAMAccountName={0})'
2018-03-06 17:40:22.690 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.ldap.SpringSecurityLdapTemplate : Found DN: CN=user\, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com
2018-03-06 17:40:22.694 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.l.a.BindAuthenticator : Attempting to bind as cn=user\, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com
2018-03-06 17:40:22.695 DEBUG 7308 --- [nio-8080-exec-5] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user cn=user\, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com
2018-03-06 17:40:22.717 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.l.a.BindAuthenticator : Retrieving attributes...
2018-03-06 17:40:22.719 DEBUG 7308 --- [nio-8080-exec-5] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Getting authorities for user cn=user\, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com
2018-03-06 17:40:22.720 DEBUG 7308 --- [nio-8080-exec-5] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Searching for roles for user 'user', DN = 'cn=user\, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com', with filter (uniqueMember={0}) in search base ''
2018-03-06 17:40:22.721 DEBUG 7308 --- [nio-8080-exec-5] o.s.s.ldap.SpringSecurityLdapTemplate : Using filter: (uniqueMember=cn=user\5c, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com)
2018-03-06 17:40:22.760 DEBUG 7308 --- [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2018-03-06 17:40:22.762 DEBUG 7308 --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2018-03-06 17:40:22.764 ERROR 7308 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; remaining name ''
I'm pretty stumped what its expecting from me. All the other questions I viewed with this error seemed like their queries were failing. But this one is clearly succeeding in finding the user before the error is generated.
Edit:
I had them create me a test user with no comma in the name since that was a focus point for people. That doesn't seem to resolve the error.
2018-03-07 13:59:52.569 DEBUG 1384 --- [nio-8080-exec-9] o.s.s.ldap.SpringSecurityLdapTemplate : Found DN: CN=Test TESTER1,ou=AAA,ou=BBBB,ou=users,dc=dom,dc=company,dc=com
2018-03-07 13:59:52.571 DEBUG 1384 --- [nio-8080-exec-9] o.s.s.l.a.BindAuthenticator : Attempting to bind as cn=Test TESTER1,ou=AAA,ou=BBBB,ou=users,dc=dom,dc=company,dc=com
2018-03-07 13:59:52.572 DEBUG 1384 --- [nio-8080-exec-9] s.s.l.DefaultSpringSecurityContextSource : Removing pooling flag for user cn=Test TESTER1,ou=AAA,ou=BBBB,ou=users,dc=dom,dc=company,dc=com
2018-03-07 13:59:52.588 DEBUG 1384 --- [nio-8080-exec-9] o.s.s.l.a.BindAuthenticator : Retrieving attributes...
2018-03-07 13:59:52.591 DEBUG 1384 --- [nio-8080-exec-9] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Getting authorities for user cn=Test TESTER1,ou=AAA,ou=BBBB,ou=users,dc=dom,dc=company,dc=com
2018-03-07 13:59:52.591 DEBUG 1384 --- [nio-8080-exec-9] .s.s.l.u.DefaultLdapAuthoritiesPopulator : Searching for roles for user 'TESTER1', DN = 'cn=Test TESTER1,ou=AAA,ou=BBBB,ou=users,dc=dom,dc=company,dc=com', with filter (uniqueMember={0}) in search base ''
2018-03-07 13:59:52.591 DEBUG 1384 --- [nio-8080-exec-9] o.s.s.ldap.SpringSecurityLdapTemplate : Using filter: (uniqueMember=cn=Test TESTER1,ou=AAA,ou=BBBB,ou=users,dc=dom,dc=company,dc=com)
2018-03-07 13:59:52.614 DEBUG 1384 --- [nio-8080-exec-9] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2018-03-07 13:59:52.615 DEBUG 1384 --- [nio-8080-exec-9] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2018-03-07 13:59:52.616 ERROR 1384 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
org.springframework.ldap.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; remaining name ''
It looks as though it is trying to read group memberships for your user, but somehow the user's DN is garbled:
Found DN: CN=user\, lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com
and then the search for memberships is done using:
Using filter: (uniqueMember=cn=user\,lname.,OU=AAA,OU=BBBB,OU=users,DC=dom,DC=company,DC=com)
It is stripping the space from the user's CN, hence Active Directory is telling you 2001 (NO_OBJECT) - and rightly so.
Seems like a bug in the LDAP handler somewhere.
EDIT
Looking at https://github.com/spring-projects/spring-security/blob/master/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java it would seem that the following code snippet from searchForMultipleAttributeValues() is reformatting your user DN:
for (int i = 0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
}
String formattedFilter = MessageFormat.format(filter, encodedParams);
logger.debug("Using filter: " + formattedFilter);
So it is either the LdapEncoder.filterEncode() call or the call to MessageFormat.format(). Definitely a bug in Spring LDAP.
I want to use LDAP authentication in my application. The examples I have come across use a simple AD structure. The company AD I want to authenticate against uses the structure below:
The "OU=Users" is where the users exist and I want to search against. This as seen is present at multiple places in nested OUs. I did try setting up authentication, but it keeps failing saying Bad credentials even though they are right. Is this due to the structure we have ?
The configuration looks like this:
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource().url("ldap://url/DC=example,DC=com")
.managerDn("sampleaccount,OU=ServiceAccounts,DC=example,DC=com").managerPassword("password")
.and().userSearchBase("DC=example,DC=com").userSearchFilter("(sAMAccountName={0})");
}
}
Any suggestions on how to go about this ?
EDIT:
The logs show this:
2016-06-14 15:00:26.948 DEBUG 13792 --- [nio-8080-exec-7] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.ldap.authentication.LdapAuthenticationProvider
2016-06-14 15:00:26.948 DEBUG 13792 --- [nio-8080-exec-7] o.s.s.l.a.LdapAuthenticationProvider : Processing authentication request for user: testUser
2016-06-14 15:00:26.948 DEBUG 13792 --- [nio-8080-exec-7] o.s.s.l.s.FilterBasedLdapUserSearch : Searching for user 'testUser', with user search [ searchFilter: '(sAMAccountName={0})', searchBase: 'OU=OU1,OU=OU2,OU=OU3,OU=OU4,DC=example,DC=com', scope: subtree, searchTimeLimit: 0, derefLinkFlag: false ]
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] .s.a.DefaultAuthenticationEventPublisher : No event was found for the exception org.springframework.security.authentication.InternalAuthenticationServiceException
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for failed: org.springframework.security.authentication.InternalAuthenticationServiceException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100238, problem 2001 (NO_OBJECT), data 0, best match of:
'DC=example,DC=com'
]; nested exception is javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-03100238, problem 2001 (NO_OBJECT), data 0, best match of:
'DC=example,DC=com'
]; remaining name 'ou=Users,ou=OU2,ou=OU3,ou=OU4,dc=example,dc=com'
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.HttpStatusEntryPoint#2bb83072
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#7a9711fa
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2016-06-14 15:00:27.742 DEBUG 13792 --- [nio-8080-exec-7] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed