Spring Security Active Directory LDAP Authentication without full name - java

Using Spring Security 3.2 I have configured ActiveDirectoryLdapAuthenticationProvider. I am able to authenticate using full name example sharon#mydomain.com but when I try to authenticate with just username 'sharon' I get the below error
2015-12-21_17:07:00.752 DEBUG o.s.s.l.a.a.ActiveDirectoryLdapAuthenticationProvider - authenticate - Processing authentication request for user: sharon
2015-12-21_17:07:00.793 DEBUG o.s.s.l.SpringSecurityLdapTemplate - searchForSingleEntryInternal - Searching for entry under DN '', base = 'dc=mydomain,dc=com', filter = '(&(objectClass=user)(userPrincipalName={0}))'
2015-12-21_17:07:00.793 INFO o.s.s.l.SpringSecurityLdapTemplate - searchForSingleEntryInternal - Ignoring PartialResultException
2015-12-21_17:07:00.794 DEBUG o.s.s.l.a.LdapAuthenticationProvider - authenticate - Processing authentication request for user: gdcadmin
2015-12-21_17:07:00.796 DEBUG o.s.s.l.a.BindAuthenticator - bindWithDn - Attempting to bind as cn=gdcadmin,cn=Users,dc=mydomain,dc=com,dc=springframework,dc=org
2015-12-21_17:07:00.796 DEBUG o.s.s.l.DefaultSpringSecurityContextSource - setupEnvironment - Removing pooling flag for user cn=gdcadmin,cn=Users,dc=mydomain,dc=com,dc=springframework,dc=org
2015-12-21_17:07:00.858 DEBUG o.a.m.f.codec.ProtocolCodecFilter - messageReceived - Processing a MESSAGE_RECEIVED for session 1
2015-12-21_17:07:00.859 DEBUG o.a.d.shared.asn1.ber.Asn1Decoder - decode - >>>==========================================
.....
.....
.....
015-12-21_17:07:00.905 DEBUG o.s.s.l.a.BindAuthenticator - handleBindException - Failed to bind as cn=gdcadmin,CN=Users,DC=mydomain,DC=com: org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - cannot bind the principalDn.]; nested exception is javax.naming.AuthenticationException: [LDAP: error code 49 - cannot bind the principalDn.]
As per spring security document :
A user named "Sharon", for example, would then be able to authenticate
by entering either the username sharon or the full Active Directory
userPrincipalName, namely sharon#mydomain.com
my configuration
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
auth.eraseCredentials(false);
auth.ldapAuthentication().userDnPatterns("cn={0},CN=Users,DC=mydomain,DC=com");
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(env.getProperty("mydomain.com"),
env.getProperty("ldap://hmidir01.mydomain.com:389/"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper);
return provider;
}
What is the mistake in my configuration.

You can implement in given ways :
1) Without persisting data into our database
WebSecurityConfig .java
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(<ldap-domain>,<ldap-url>);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
#Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")
.antMatchers("/user/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/rest/**", "/css/**", "/fonts/**", "/images/**", "/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/").failureUrl("/?error").successHandler("/home").permitAll()
.usernameParameter("emailId").passwordParameter("password")
.and()
.logout()
.logoutUrl("/logout").logoutSuccessUrl("/").permitAll()
.and()
.exceptionHandling().accessDeniedPage("/home")
.and()
.csrf()
.and()
.httpBasic();
}
}
2) With persisting data into our database
WebSecurityConfig.java
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")
.antMatchers("/user/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/rest/**", "/css/**", "/fonts/**", "/images/**", "/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/").failureUrl("/?error").successHandler("/home").permitAll()
.usernameParameter("emailId").passwordParameter("password")
.and()
.logout()
.logoutUrl("/logout").logoutSuccessUrl("/").permitAll()
.and()
.exceptionHandling().accessDeniedPage("/home")
.and()
.csrf()
.and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)throws Exception {
auth
.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(<ldap-domain>(null), <ldap-url>);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new AttributesLDAPUserDetailsContextMapper();
}
}
AttributesLDAPUserDetailsContextMapper.java
public class AttributesLDAPUserDetailsContextMapper implements UserDetailsContextMapper {
#Autowired
private UserService service;
private InetOrgPersonContextMapper ldapUserDetailsMapper = new InetOrgPersonContextMapper();
#Override
public UserDetails mapUserFromContext(DirContextOperations dirContextOperations, String userName, Collection<? extends GrantedAuthority> collection) {
InetOrgPerson userLdap = (InetOrgPerson) ldapUserDetailsMapper.mapUserFromContext(dirContextOperations, userName, collection);
User user = service.findOne(userLdap.getUsername());
if (user == null) {
user = new Usere();
user.setName(StringUtils.defaultString(userLdap.getDisplayName()).trim());
user.setEmailId(StringUtils.defaultString(userLdap.getUsername()).trim());
user.setdescription(StringUtils.defaultString(userLdap.getDescription()).trim());
user.setIsAdmin(false);
user.setIsEmployee(true);
service.save(user);
}
return new LdapSecuredUser(user);
}
#Override
public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) {
ldapUserDetailsMapper.mapUserToContext(userDetails, dirContextAdapter);
}
}
LdapSecuredUser.java
public class LdapSecuredUser extends User implements LdapUserDetails {
private static final long serialVersionUID = -8997460180274787521L;
public LdapSecuredUser(User user) {
if (user != null) {
this.setId(user.getId());
this.setEmailId(user.getEmailId());
this.setName(user.getName());
this.setdescription(user.getDescription());
this.setIsAdmin(user.getIsAdmin());
this.setIsEmployee(user.getIsEmployee());
}
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("USER"));
if(super.getIsAdmin())
authorities.add(new SimpleGrantedAuthority("ADMIN"));
return authorities;
}
#Override
public String getUsername() {
return super.getEmailId();
}
#Override
public String getPassword() {
return null;
}
#Override
public String getDn() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}

With spring security 5.2.1:
You can use the setSearchFilter() function.
The LDAP auth process has two main steps: binding, which uses the domain parameter (the first) from ActiveDirectoryLdapAuthenticationProvider()to form the username like this: myUser#sub.domain.com
given credentials: username= myUser; password myPassword.
If this is is not correct, you'll get bad credential error (AcceptSecurityContext error, data 52e).
Then the next step is to finding your user in the ldap directory.
If your user doesn't have an attribute named username which is = myUser#sub.domain.com, the ldap server will give you back a not found error (this will pop up in your log as Ignoring PartialResultException from an UsernameNotFoundException: User myUser not found in directory. exception. For this you can use the searchFilter option.
The provided username will be inserted at the {1} point.
#Configuration
#EnableWebSecurity
#AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception{
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().fullyAuthenticated().and().httpBasic(); //this will invoke an auth popup in browser
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new
ActiveDirectoryLdapAuthenticationProvider("sub.domain.com","ldap://url");
provider.setSearchFilter("mailNickname={1}"); //here is the trick
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}

Related

How to resolve error spring boot oauth2 google in heroku?

I am using Google OAuth to login into my springboot apps. It works perfectly in my local computer. But it shows this error when I deploy it to heroku.
Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' that could not be found.
Action: Consider defining a bean of type 'org.springframework.security.oauth2.client.registration.ClientRegistrationRepository' in your configuration.
Here is my WebSecurityConfiguration.
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDao userDao;
#Autowired
private PenggunaService penggunaService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/login", "/oauth/**", "/js/**","/css/**","/image/**").permitAll()
.antMatchers("/admin").hasAnyAuthority("admin")
.anyRequest().authenticated()
.and()
.oauth2Login()
.userInfoEndpoint()
.userAuthoritiesMapper(authoritiesMapper())
.and()
.successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication auth) throws IOException, ServletException {
DefaultOidcUser oauthUser = (DefaultOidcUser) auth.getPrincipal();
String email = oauthUser.getAttribute("email");
penggunaService.processOAuthPostLogin(email, auth);
response.sendRedirect("/");
}
})
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/").permitAll();
}
private GrantedAuthoritiesMapper authoritiesMapper(){
return (authorities) -> {
String emailAttrName = "email";
String email = authorities.stream()
.filter(OAuth2UserAuthority.class::isInstance)
.map(OAuth2UserAuthority.class::cast)
.filter(userAuthority -> userAuthority.getAttributes().containsKey(emailAttrName))
.map(userAuthority -> userAuthority.getAttributes().get(emailAttrName).toString())
.findFirst()
.orElse(null);
if (email == null) {
return authorities; // data email tidak ada di userInfo dari Google
}
Pengguna user = userDao.findByEmailPengguna(email);
if(user == null) {
return authorities; // email user ini belum terdaftar di database
}
List<Permission> userAuthorities = user.getRole().getPermissions();
if (userAuthorities.isEmpty()) {
return authorities; // Return the 'unmapped' authorities
}
return Stream.concat(
authorities.stream(),
userAuthorities.stream()
.map(Permission::getIdPermission)
.map(SimpleGrantedAuthority::new)
).collect(Collectors.toCollection(ArrayList::new));
};
}
#Bean
public SpringSecurityDialect springSecurityDialect() {
return new SpringSecurityDialect();
}
}
Thank you in advance.

How can I resolve 'Unresolved compilation problems for authenticationFailureHandler' in Spring Security?

I am using Spring Security to build an authentication entry point for my web app. Now mr registration works well aside for the fact that a user is unable to login due to a compilation error resulting from my successHandler() and failureHandler() method.
The error logged is: java.lang.Error: Unresolved compilation problems:
successHandler cannot be resolved to a variable
authenticationFailureHandler cannot be resolved to a variable
I am not sure what I am doing wrong. I am pasting the security configuration code of my spring boot app. Where do I need to add the required variable or parameters (if any) in order to resolve this?
I've tried to create 2 variables with private modifiers that denote to the same parameters of the Handler which still doesn't work
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private DataSource dataSource;
#Value("${spring.queries.users-query}")
private String usersQuery;
#Value("${spring.queries.roles-query}")
private String rolesQuery;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.
jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/signup_employer").permitAll()
.antMatchers("/registrations").permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN").anyRequest()
.authenticated().and().csrf().disable()
.formLogin()
.loginPage("/login").failureUrl("/login?error=true")
.defaultSuccessUrl("/admin")
.usernameParameter("email")
.passwordParameter("password")
.successHandler(successHandler)
.failureHandler(authenticationFailureHandler)
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout").deleteCookies("JSESSIONID").deleteCookies("my-rememberme")
.logoutSuccessHandler(logoutSuccessHandler())
.and().rememberMe()
.tokenRepository(persistentTokenRepository())
.and()
// .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
//.and()
.headers().cacheControl().disable()
.and().sessionManagement()
.sessionFixation().migrateSession()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/invalidSession")
.maximumSessions(1)
.expiredUrl("/invalidSession");
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
tokenRepositoryImpl.setDataSource(dataSource);
return tokenRepositoryImpl;
}
#Bean
public LogoutSuccessHandler logoutSuccessHandler() {
return new CustomLogoutSuccessHandler();
}
#Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
#Bean
public AuthenticationEntryPoint unauthorizedEntryPoint() {
return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**", "/static/**", "/css/**", "/email_templates/**", "/error/**", "/font-awesome/**", "/fonts/**", "/res/**", "/vendor/**", "/js/**", "/img/**");
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
}
login success handler:
public class MySimpleUrlAuthenticationSuccessHandler implements
AuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
protected int SessionTimeout = 1 * 60;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public MySimpleUrlAuthenticationSuccessHandler() {
super();
}
// API
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final
HttpServletResponse response, final Authentication authentication) throws
IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
// IMPL
protected void handle(final HttpServletRequest request, final
HttpServletResponse response, final Authentication authentication) throws
IOException {
final String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug("Response has already been committed. Unable to
redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(final Authentication authentication) {
boolean isUser = false;
boolean isAdmin = false;
final Collection<? extends GrantedAuthority> authorities =
authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("USER")) {
isUser = true;
break;
} else if (grantedAuthority.getAuthority().equals("ADMIN")) {
isAdmin = true;
break;
}
}
if (isUser) {
return "/homepage.html";
} else if (isAdmin) {
return "/admin";
} else {
throw new IllegalStateException();
}
}
/**
* Removes temporary authentication-related data which may have been stored
in the session
* during the authentication process.
*/
protected final void clearAuthenticationAttributes(final HttpServletRequest
request) {
final HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
protected RedirectStrategy getRedirectStrategy() {
return redirectStrategy;
}
public void setRedirectStrategy(final RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
}
Those two lines inside the configure(HttpSecurity) method refers to properties/variables which do not seem to exist.
.successHandler(successHandler)
.failureHandler(authenticationFailureHandler)
I see you've created your MySimpleUrlAuthenticationSuccessHandler. Provide an instance of that class to successHandler. And do the same with failureHandler with an instance of your custom/bundled AuthenticationFailureHandler.
I suppose that the warning you mention requires defining the AuthenticationSuccessHandler as Bean.
#Configuration
class MyConfigurationClass {
...
#Bean
AuthenticationSuccessHandler myAuthenticationSuccessHandler() {
return new MyCustomOrBundledAuthenticationSuccessHandler();
}
}
You can then
.successHandler(myAuthenticationSuccessHandler())

spring auth error No AuthenticationProvider

I'm trying to build auth system with Spring Boot Security.
So I have custom auth provider (without #Component annotation)
public class CustomAuthProvider extends DaoAuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
if (authentication.isAuthenticated()) {
return authentication;
}
if ("user".equals(name) && "password".equals(password)) {
return new UsernamePasswordAuthenticationToken(
name, password, new ArrayList<GrantedAuthority>(Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"))));
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
which declared as a bean
#Bean
public DaoAuthenticationProvider authProvider() {
final CustomAuthProvider authProvider = new CustomAuthProvider();
authProvider.setUserDetailsService(userDetailsService);
return authProvider;
}
Here is configuration:
#Override
protected void configure(
AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/anonymous*").anonymous()
.antMatchers("/login*").permitAll()
.antMatchers("/user/registration*").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginProcessingUrl("/login");
}
When I'm trying post query to localhost:8080/login I'm getting sign-in form with message
No AuthenticationProvider found for
org.springframework.security.authentication.UsernamePasswordAuthenticationToken
spring's default request parameters for authentication are: username and password
you are sending email instead of username so it throws an exception somewhere in your authentication manager.
if you want to override the default values you can simply specify that in your HTTP security configs:
.formLogin()
.loginProcessingUrl("/login")
.usernameParameter("email")
.passwordParameter("password")

OAuth2-SpringBoot - Refresh token

I have configured my spring boot application to to provide oauth2 authorization.
#Configuration
public class OAuth2Configuration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
.disable()
.headers()
.frameOptions().disable()
.exceptionHandling().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll()
.antMatchers("/api/v1/admin/**").permitAll()
.antMatchers("/api/v1/test/**").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/api/**").authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Autowired
private DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(Authorities.ROLE_USER.name())
.authorizedGrantTypes("password", "refresh_token", "authorization_code", "implicit")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(
propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800))
.refreshTokenValiditySeconds(100000);
}
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
}
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Bean
public CustomPasswordEncoder passwordEncoder() {
return new CustomPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**").antMatchers("/api/login/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.httpBasic().realmName("WebServices").and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.requestMatchers().antMatchers("/oauth/authorize").and()
.authorizeRequests().antMatchers("/oauth/authorize")
.authenticated();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
}
public class UserDetailsServiceImpl implements UserDetailsService {
#Inject
private AccountDao accountDao;
#Override
#Transactional
public UserDetails loadUserByUsername(final String login) {
Account userFromDatabase = null;
String lowercaseLogin = login.toLowerCase();
if (lowercaseLogin.contains("#")) {
userFromDatabase = accountDao.getByEmailId(lowercaseLogin);
} else {
userFromDatabase = accountDao.getByPhoneNumber(lowercaseLogin);
}
if (userFromDatabase != null) {
if (!userFromDatabase.getActivated()) {
throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated");
}
List<GrantedAuthority> grantedAuthorities = userFromDatabase.getRoles().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getRoleName())).collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(userFromDatabase.getAccountName(),
userFromDatabase.getAccountPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the " + "database");
}
}
}
Now whenever I try to get the refresh token after the access token expires, I always get
2017-07-10 00:57:40.797 INFO 68115 --- [nio-9090-exec-4]
o.s.s.o.provider.endpoint.TokenEndpoint : Handling error:
NoSuchClientException, No client with requested id: 12345678
Though there is a row in the db with the column phone number 12345678 and account name as 12345678.
https://myTestWebServices/oauth/token?grant_type=refresh_token&refresh_token=f4cc8213-3f2b-4a30-965b-6feca898479e
I have the header set to Authorization: Basic xxx
xxx is the same that I use to get the access_token so I am assuming it works fine.
But the output is always this
{ "error": "unauthorized", "error_description": "User 12345678 was
not found in the database" }
You should be passing clientId and client secret (these are different from userId and password) while fetching access token using refresh token. Not sure what are passing in authorisation headers.
You seem to be having two different issues. When do you get the below error:
{ "error": "unauthorized", "error_description": "User 12345678 was not
found in the database" }
Can you verify if the user is successfully authenticated and if the service returned access token and refresh token? You may place debug pointer in UserDetailsService and check the flow.
Try to validate the configuration by following below steps:
Get Refresh Token, assuming you are using
curl -vu clientId:clientSecret 'http://your_domain_url/api/oauth/token?username=userName&password=password&grant_type=password'
here username and password are different from client ID and client secret
This should return you refresh token and access token in the response
{"access_token":"d5deb98a-75fc-4f3a-bbfd-e5c87ca2ca6f","token_type":"bearer","refresh_token":"b2be4291-57e9-4b28-b114-feb3406e030d","expires_in":2,"scope":"read write"}
The above response has got access token and refresh token. Whenever the access token expires, you can use refresh token to fetch access token like below:
curl -vu clientId:clientSecret 'http://your_domain_url/api/oauth/token?grant_type=refresh_token&refresh_token=refresh_token_value'
Response:
{"access_token":"13fd30f9-f0c5-414e-9fbd-a5e2f9f3e4a7","token_type":"bearer","refresh_token":"b2be4291-57e9-4b28-b114-feb3406e030d","expires_in":2,"scope":"read write"}
Now you can use the access token make your service calls
curl -i -H "Authorization: Bearer 13fd30f9-f0c5-414e-9fbd-a5e2f9f3e4a7" http://your_domain_url/api/mySecureApi
I think for the password grant_type, a clientId and clientSecret are required. You pass the Base64 encoded clientId and clientSecret instead of the Access Token in the Authorization header. Like so:
curl -H "Authorization: Bearer [base64encode(clientId:clientSecret)]" "https://yourdomain.com/oauth/token?grant_type=refresh_token&refresh_token=[yourRefreshToken]"
I'm assuming you first get the token like this (which you didn't say even though I asked):
curl --data "grant_type=password&username=user&password=pass&client_id=my_client" http://localhost:8080/oauth/token"
Also, put a breakpoint in loadUserByUsername and check if it's invoked for the failed refresh attempt.
Just add UserDetailsService then it will work
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
and request
http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=myclient&client_secret=secret&refresh_token=<token>

Spring security switch to Ldap authentication and database authorities

I implemented database authentication for my web page and web service.
It work well for both, now I have to add Ldap authentication.
I have to authenticate through remote Ldap server (using username and password) and if the user exists I have to use my database for user roles (in my database username is the same username of Ldap).
So I have to switch from my actual code to the Ldap and database authentication as above explained. My code is:
SecurityConfig class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //Authorize Request Configuration
//the / and /register path are accepted without login
//.antMatchers("/", "/register").permitAll()
//the /acquisition/** need admin role
//.antMatchers("/acquisition/**").hasRole("ADMIN")
//.and().exceptionHandling().accessDeniedPage("/Access_Denied");
//all the path need authentication
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login")
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
// CSRF tokens handling
}
}
MyUserDetailsService class
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserServices userServices;
static final Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class);
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username){
try{
com.domain.User user = userServices.findById(username);
if (user==null)
LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : User doesn't exist" );
else{
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
}catch(Exception e){
LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
return null;
}
// Converts com.users.model.User user to
// org.springframework.security.core.userdetails.User
private User buildUserForAuthentication(com.domain.User user, List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
so I have to:
1)access of user from login page for web pages and username and password for web services. This has to be done through Ldap.
2)the username of user needs for database query to authenticate user.
Do you have any idea how I can implement this?
Thanks
UPDATE WITH RIGHT CODE: Following the #M. Deinum advice I create MyAuthoritiesPopulator class instead of MyUserDetailsService and authentication with database and Ldap works:
#Service("myAuthPopulator")
public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Autowired
private UserServices userServices;
static final Logger LOG = LoggerFactory.getLogger(MyAuthoritiesPopulator.class);
#Transactional(readOnly=true)
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
try{
com.domain.User user = userServices.findById(username);
if (user==null)
LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : User doesn't exist into ATS database" );
else{
for(UserRole userRole : user.getUserRole()) {
authorities.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
}
return authorities;
}
}catch(Exception e){
LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
return authorities;
}
}
and I changed SecurityConfig as below:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("myAuthPopulator")
LdapAuthoritiesPopulator myAuthPopulator;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url("ldap://127.0.0.1:10389/dc=example,dc=com")
// .managerDn("")
// .managerPassword("")
.and()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.ldapAuthoritiesPopulator(myAuthPopulator);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
//Excluede send file from authentication because it doesn't work with spring authentication
//TODO add java authentication to send method
.antMatchers(HttpMethod.POST, "/client/file").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //Authorize Request Configuration
//the "/" and "/register" path are accepted without login
//.antMatchers("/", "/register").permitAll()
//the /acquisition/** need admin role
//.antMatchers("/acquisition/**").hasRole("ADMIN")
//.and().exceptionHandling().accessDeniedPage("/Access_Denied");
//all the path need authentication
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login")
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
}
My LDAP development environment created in Apache directory studio
Spring Security already supports LDAP out-of-the-box. It actually has a whole chapter on this.
To use and configure LDAP add the spring-security-ldap dependency and next use the AuthenticationManagerBuilder.ldapAuthentication to configure it. The LdapAuthenticationProviderConfigurer allows you to set the needed things up.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url(...)
.port(...)
.managerDn(...)
.managerPassword(...)
.and()
.passwordEncoder(passwordEncoder())
.userSearchBase(...)
.ldapAuthoritiesPopulator(new UserServiceLdapAuthoritiesPopulater(this.userService));
}
Something like that (it should give you at least an idea on what/how to configure things) there are more options but check the javadocs for that. If you cannot use the UserService as is to retrieve the roles (because only the roles are in the database) then implement your own LdapAuthoritiesPopulator for that.
You need to create a CustomAuthenticationProvider wich implements AuthenticationProvider, and override authenticate method, for example:
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
boolean authenticated = false;
/**
* Here implements the LDAP authentication
* and return authenticated for example
*/
if (authenticated) {
String usernameInDB = "";
/**
* Here look for username in your database!
*
*/
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(usernameInDB, password, grantedAuths);
return auth;
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Then, in your SecurityConfig, you need to override the configure thats use AuthenticationManagerBuilder:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
You can autowire the CustomAuthenticationProvider doing this:
#Autowired
private CustomAuthenticationProvider authenticationProvider;
Doing this, you can override the default authentication behaviour.
I also found this chapter Spring Docu Custom Authenicator and build my own switch between LDAP and my DB users. I can effortlessy switch between login data with set priorities (in my case LDAP wins).
I have configured an LDAP with the yaml configuration files for the LDAP user data which I don't disclose here in detail. This can be easily done with this Spring Docu LDAP Configuration.
I stripped the following example off the clatter such as logger/javadoc etc. to highlight the important parts. The #Order annotation determines the priorities in which the login data is used. The in memory details are hardcoded debug users for dev only purposes.
SecurityWebConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private Environment env;
#Inject
private LdapConfiguration ldapConfiguration;
#Inject
private BaseLdapPathContextSource contextSource;
#Inject
private UserDetailsContextMapper userDetailsContextMapper;
#Inject
private DBAuthenticationProvider dbLogin;
#Inject
#Order(10) // the lowest number wins and is used first
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new InMemoryUserDetailsManager(getInMemoryUserDetails()));
}
#Inject
#Order(11) // the lowest number wins and is used first
public void configureLDAP(AuthenticationManagerBuilder auth) throws Exception {
if (ldapConfiguration.isLdapEnabled()) {
auth.ldapAuthentication().userSearchBase(ldapConfiguration.getUserSearchBase())
.userSearchFilter(ldapConfiguration.getUserSearchFilter())
.groupSearchBase(ldapConfiguration.getGroupSearchBase()).contextSource(contextSource)
.userDetailsContextMapper(userDetailsContextMapper);
}
}
#Inject
#Order(12) // the lowest number wins and is used first
public void configureDB(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(dbLogin);
}
}
DB Authenticator
#Component
public class DBAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// your code to compare to your DB
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
/**
* #param original <i>mandatory</i> - input to be hashed with SHA256 and HEX encoding
* #return the hashed input
*/
private String sha256(String original) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new AuthException("The processing of your password failed. Contact support.");
}
if (false == Strings.isNullOrEmpty(original)) {
md.update(original.getBytes());
}
byte[] digest = md.digest();
return new String(Hex.encodeHexString(digest));
}
private class AuthException extends AuthenticationException {
public AuthException(final String msg) {
super(msg);
}
}
}
Feel free to ask details. I hope this is useful for someone else :D
For anyone using grails it is much simpler. Simply add this to your config:
grails:
plugin:
springsecurity:
ldap:
authorities:
retrieveDatabaseRoles: true

Categories

Resources