I am trying to secure an application with spring security . Usually I would implement a custom UserDeatilsService and a CustomAuthenticationManager . But in this case as UserDetailsService only has one function loadUserDeatils(String email) . The problem is the class I have to use to retrieve the details of user does't have a function to load the details by only username . It need to have two parameters like username and password . So I went back to only implement a CustomAuthenticationManager and autowire the class there and get the details there itself and check it against the authentication ,but I am not being able to autowire it and it shows null . How can I achieve this or is there a better way to do this ??
MY Security Config :
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*#Autowired
MetadataGeneratorFilter metadataGeneratorFilter;
#Autowired
FilterChainProxy samlFilter;
#Autowired
SAMLEntryPoint samlEntryPoint;
*/
#Autowired
private CustomUserDetailsService customUserDetailsService;
/* #Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("metadataGeneratorFilter.toString() + samlEntryPoint.toString() + samlFilter.toString()+sdfsdf");
auth
.inMemoryAuthentication()
.withUser("user1")
.password("password1")
.roles("user")
.and()
.withUser("admin")
.password("password")
.roles("admin","user");
}*/
#Override
protected void configure(HttpSecurity http) throws Exception {
try {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/static/**").permitAll()
.antMatchers("/settings/api/**").permitAll()
.antMatchers("/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/login")
// .usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/index",true)
.and()
.httpBasic();
// .defaultSuccessUrl("/", "true);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("sadhiasdniaaaaaaaaaaaaaaaa:");
e.printStackTrace();
}
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new ApiCustomAuthenticationProvider());
}
#Bean
public ApiCustomAuthenticationProvider apiCustomAuthenticationProvider() {
return new ApiCustomAuthenticationProvider();
}
#Bean
public SACSClientService sacsClientService(){
return new SACSClientService();
}
}
My CustomAuthenticationProvider :
#Component
#Service
public class ApiCustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private SACSClientService sacsClientService;
#Autowired
private ReferralDaoImpl referralDaoImpl;
private ReferralProperty referralProperty =null ;
/*#PostConstruct
public void init()
{
ReferralProperty referralProperty = referralDaoImpl.getReferralProperty("sacs.service.base.url");
// sacsClientService.setWebServiceBaseUrl("http://10.41.92.43:11140");
sacsClientService.setWebServiceBaseUrl(referralProperty.getValue());
}*/
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
/* if(referralProperty==null)
referralProperty = referralDaoImpl.getReferralProperty("sacs.service.base.url");
// sacsClientService.setWebServiceBaseUrl("http://10.41.92.43:11140");
sacsClientService.setWebServiceBaseUrl(referralProperty.getValue());
if(sacsClientService == null )
System.out.println("***** Response 12" + "*****" );
if(referralDaoImpl ==null)
System.out.println("***** Response2134214234 " + "*****" );*/
// GetUserDetailsRequest request = new GetUserDetailsRequest(username, password);
System.out.println(username + password + "sadfkjgas" + "user.getName() " );
// AuthenticateAndGetUserRequest sacsRequest = new AuthenticateAndGetUserRequest(username,password);
// AuthenticateAndGetUserResponse sacsResponse = sacsClientService.authenticateAndGetUser(sacsRequest);
boolean loginStatus = false;
GetUserDetailsResponse userDetailsResponse = sacsClientService.authenticateAndGetUser(request);
UserSRO user = userDetailsResponse.getUser();*/
/* if (user != null) {
Set<AppRoleSRO> roles = user.getUserAppRoles();
List<GrantedAuthority> authorities = null;
if( roles !=null && !roles.isEmpty()) {
authorities = new ArrayList<GrantedAuthority>();
for (AppRoleSRO role : roles){
authorities.add(new SimpleGrantedAuthority(role.getAppRoleName()));
}
}
return new UsernamePasswordAuthenticationToken(username,password,authorities);
}else {
throw new BadCredentialsException("User does not exist on SACS");
}
}*/
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken("aman", "12345",grantedAuths);
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
SACSCliemtService is what I am having trouble autowiring . As soon as I use it it gives a error with one of the filters. The code works well when I comment out the SACSClient usage and pass dummy values
Related
I am currently working on a spring boot project that has multiple security (authentication) configurations combined:
a rest api with http basic auth
a rest api with jwt auth.
a web (form login) with 2fa auth.
The problem I am experiencing is, that the configurations cannot be entirely seperated. More specific: The authenticiation providers are accumulated (in the provider manager), which prevents my preferred setup from working correctly.
What happens is this: In config (3) i have a custom 2fa authentication provider which checks if the credentials (both password and 2FA code!) are entered correctly. If the password is correct, but the 2fa code is not, it exits (catches) with a (authentication or bad credentials) exception. However, as it exits the 2fa authentication provider, it goes back to the provider manager. The latter has another (DAO) auth provider at hand, and checks the credentials again: but this time: only password! not the 2fa code!). As the password is ok, it authorized the request.
So for short: I am experiencing a problem where the first authentication provider does NOT authorize, but the second does (because that one does not take the 2fa code into account)!
I have been at this for days, and i cannot seem to get this right.
As an alternative i have now opted for a solution using a 2fa custom filter. But is not my preferred solution, as it gives me some frontend problems (i first have to authorize the username/password, and only after that i can check the 2fa code).
Is there a solution using my 2fa auth provider? I would sort of wish that the auth providers would not get accumulated, so that when the first auth. provider exits with bad credentials, the auth procedure ends with a 'bad credentials'.
My config class:
WebSecurityConfig.java
#EnableWebSecurity
#Configuration
#AllArgsConstructor
public class WebSecurityConfig_backup extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private PasswordEncoder passwordEncoder;
private HttpBasicAuthenticationExceptionHandler authenticationExceptionHandler;
private JwtAuthenticationProvider jwtAuthenticationProvider;
private MfaAuthenticationDetailsSource mfaAuthenticationDetailsSource;
private MfaAuthenticationProvider2 mfaAuthenticationProvider;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Configuration
#Order(1)
public class ApiV1WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**")
.httpBasic()
.authenticationEntryPoint(authenticationExceptionHandler)
.and()
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v1/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth .userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(2)
public class ApiV2WebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v2/**")
.addFilter(bearerTokenAuthenticationFilter())
.addFilter(credentialsAuthenticationFilter())
.csrf().disable()
.authorizeRequests(authorize -> {
authorize
.mvcMatchers(HttpMethod.GET,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/api/v2/transaction/**").hasAnyRole("BANK", "ADMIN");
//.anyRequest().denyAll();
})
.authorizeRequests()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
private BearerTokenAuthenticationFilter bearerTokenAuthenticationFilter() throws Exception {
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private CredentialsAuthenticationFilter credentialsAuthenticationFilter() throws Exception {
CredentialsAuthenticationFilter filter = new CredentialsAuthenticationFilter(authenticationManager());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
}
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, ex) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON.toString());
response.setCharacterEncoding(StandardCharsets.UTF_8.displayName());
ResponseWriterUtil.writeErrorResponse(response, ex.getMessage());
};
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
#Configuration
#Order(3)
public class FormWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//authorisation
http//.addFilterBefore(googleTfaFilter, SessionManagementFilter.class)
.authorizeRequests(authorize -> {
authorize
.mvcMatchers("/", "/login", "/logout", "/registrationPage", "/register").permitAll()
.mvcMatchers(HttpMethod.GET,
"/others1", "/others2").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.POST,
"/others1", "/others2").hasAnyRole("USER", "ADMIN");
})
.formLogin()
.loginPage("/login").permitAll()
.usernameParameter("email")
.loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/")
.failureUrl("/login?error")
.authenticationDetailsSource(mfaAuthenticationDetailsSource)
.and()
.logout()
.deleteCookies("JSESSIONID")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.logoutSuccessUrl("/login?logout").permitAll()
.and()
.sessionManagement()
.sessionFixation().migrateSession()
.and()
.headers().frameOptions().sameOrigin()
.and()
.csrf();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(mfaAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
}
}
auth provider for config 2:
#Slf4j
#RequiredArgsConstructor
#Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final UserDetailsService userDetailsService;
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(BearerTokenAuthenticationToken.class);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
Authentication auth = null;
try {
//validate the token
Jwts.parser().setSigningKey(JWT_TOKEN_SECRET).parseClaimsJws(bearerToken.getToken());
JwtTokenUtil jwtTokenUtil = new JwtTokenUtil(bearerToken.getToken());
String username = jwtTokenUtil.getUsernameFromToken();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
log.debug("Authentication token: " + auth);
} catch (IllegalArgumentException e) {
throw new UserServiceAuthenticationException("Invalid token");
} catch (ExpiredJwtException e) {
throw new UserServiceAuthenticationException("Token expired");
} catch (SignatureException e) {
throw new UserServiceAuthenticationException("Invalid signature");
}
return auth;
}
}
Auth provider for config (3)
#Slf4j
#AllArgsConstructor
#Component
public class MfaAuthenticationProvider2 implements AuthenticationProvider {
private UserRepo userRepository;
private GoogleAuthenticator googleAuthenticator;
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
String verficationCode = ((MfaAuthenticationDetails) authentication.getDetails()).getUserMFaCode();
User user = userRepository.findByEmail(authentication.getName()).stream().findFirst().orElse(null);
if(user == null || !passwordEncoder.matches(password, user.getPassword())){
throw new BadCredentialsException("Invalid username or password");
}
try {
if(!googleAuthenticator.authorizeUser(user.getUsername(), Integer.parseInt(verficationCode))){
throw new BadCredentialsException("Invalid verification code.");
}
} catch (Exception e) {
throw new BadCredentialsException("Authentication failed. Please try again.");
}
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
My current alternative solution for config 3:
#Slf4j
#Component
public class GoogleTfaFilter2 extends OncePerRequestFilter {
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private final GoogleTfaFailureHandler googleTfaFailureHandler = new GoogleTfaFailureHandler();
private final RequestMatcher urlIs2fa = new AntPathRequestMatcher("/verify2fa");
private final RequestMatcher urlIs2fa2 = new AntPathRequestMatcher("/register2fa");
private final RequestMatcher urlResource = new AntPathRequestMatcher("/resources/**");
private final RequestMatcher api = new AntPathRequestMatcher("/api/**");
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
StaticResourceRequest.StaticResourceRequestMatcher staticResourceRequestMatcher =
PathRequest.toStaticResources().atCommonLocations();
if (urlIs2fa.matches(request) || urlResource.matches(request) || urlIs2fa2.matches(request)||
staticResourceRequestMatcher.matcher(request).isMatch() || api.matches(request)) {
filterChain.doFilter(request, response);
return;
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)){
log.debug("Processing 2FA Filter");
if (authentication.getPrincipal() != null && authentication.getPrincipal() instanceof User) {
User user = (User) authentication.getPrincipal();
if (!user.getMfaPassed()) {
log.debug("2FA Required");
request.getRequestDispatcher("/verify2fa").forward(request, response);
return;
}
}
}
filterChain.doFilter(request, response);
}
}
I'm using LDAP authentication provider (active directory) + JWT authorization filter.
I have my custom user object implementing UserDetails, also my user service extends UserDetailsService. But when I do:
Usuario principal = (Usuario) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Principal is just a string (the username), not my user object.
This is my configuration:
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final LdapProperties ldapProperties;
private final LdapUserMapper ldapUserMapper;
private final UserService userService;
public SecurityConfig(LdapProperties ldapProperties, LdapUserMapper ldapUserMapper, UserService userService) {
this.ldapProperties = ldapProperties;
this.ldapUserMapper = ldapUserMapper;
this.userService = userService;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Entry points
http.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.GET, "/v2/api-docs",
"/configuration/ui",
"/swagger-resources",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/swagger-resources/**",
"/swagger-ui.html").permitAll()
//TODO review
.anyRequest().authenticated();
// JwtWebSecurityConfigurer... TODO ?
// Filters
http.addFilter(new AuthenticationFilter(authenticationManager())); // ldap
http.addFilter(new AuthorizationFilter(authenticationManager())); // jwt
http.cors();
http.csrf().disable();
// No session will be created or used by spring security
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Need to provide Authorization header
http.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider());
auth.userDetailsService(userService);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.setAllowedMethods(Arrays.asList(CorsConfiguration.ALL));
//TODO configure properly
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
#Bean
public AbstractLdapAuthenticationProvider ldapAuthenticationProvider() {
String urls = "";
for (String url : ldapProperties.getUrls()) {
urls += url + " ";
}
urls = urls.trim();
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
ldapProperties.getBaseEnvironment().get("domain"),
urls,
ldapProperties.getBase()
);
provider.setUserDetailsContextMapper(ldapUserMapper);
provider.setConvertSubErrorCodesToExceptions(true);
// comment to connect as anonymous
provider.authenticate(
new UsernamePasswordAuthenticationToken(ldapProperties.getUsername(), ldapProperties.getPassword())
);
return provider;
}
}
LdapUserMapper:
#Component
public class LdapUserMapper implements UserDetailsContextMapper {
private final UserService userService;
#Autowired
public LdapUserMapper(UserService userService) {
this.userService = userService;
}
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
Usuario result = (Usuario) userService.loadUserByUsername(username);
//TODO compare roles ? set bloqueado ? ...
return result;
}
#Override
public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) {
}
}
It was my authorization filter.
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String token = req.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.replace("Bearer ", "");
Authentication authentication = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(final String token) {
try {
DecodedJWT decodedToken = JWT.require(Algorithm.HMAC512(jwtSecret)).build().verify(token);
String username = decodedToken.getSubject();
return new UsernamePasswordAuthenticationToken(username , null, new ArrayList<>());
} catch (JWTVerificationException ex) {
log.error(ex.getMessage());
}
return null;
}
I wasn't aware that I only was passing the username as the Principal object. So I changed it to this:
Usuario usuario = (Usuario) userService.loadUserByUsername(decodedToken.getSubject());
I also removed
auth.userDetailsService(userService);
From AuthenticationManagerBuilder config. That was wrong. My auth provider is LDAP, not the DB.
I have implemented the spring security with LDAP using Spring Boot. I'm able to successfully bind with my company LDAP server but with hard-coded values. This is the only way I can bind with my company LDAP server and proceed further since I do not know the Administrator/Generic UserDN or Password to make a successful bind. The company does not provide me the Admin credentials due to some confidential reasons.
I would like to set the UserDn and Password of the ContextSource with the username and password entered by the user in the login form. But the problem here is the SecurityConfig class is scanned at the time the Tomcat server is started and later after the login process the control does not come to the SecurityConfig class at all. How can I solve this problem? Need some assistance.
This is my SecurityConfig class:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationFailureHandler customAuthFailureHandler;
#Autowired
private CustomAuthenticationSuccessHandler customAuthSuccessHandler;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/sign-in")
.usernameParameter("userid")
.passwordParameter("password")
.successHandler(customAuthSuccessHandler)
.failureHandler(customAuthFailureHandler)
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll()
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthProvider());
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationProvider ldapAuthProvider() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource("ldaps://some.domain.com:3269/");
contextSource.setUserDn("username#domain.com"); // Here I want to set the username from Login screen
contextSource.setPassword("password"); // also password from login screen
contextSource.afterPropertiesSet();
String userSearchFilter = "(sAMAccountName=username)"; // Here too I need to set username from login screen
LdapUserSearch ldapUserSearch = new FilterBasedLdapUserSearch("dc=domain,dc=com", userSearchFilter, contextSource);
BindAuthenticator bindAuth = new BindAuthenticator(contextSource);
bindAuth.setUserSearch(ldapUserSearch);
LdapAuthenticationProvider ldapAuthProvider = new LdapAuthenticationProvider(bindAuth);
return ldapAuthProvider;
}
}
I have created an AuthenticationProvider bean method and I'm setting it in the AuthenticationManagerBuilder. I also tried creating a CustomAuthenticationProvider but there again I had to check with the hard-coded username and password :(
I finally got it working.. :) I found what I want here . (Ali Miskeen's answer)
I tried with the CustomAuthenticationProvider approach itself but this time, I checked for authentication using the traditional JNDI LDAP approach. I also wanted to check for 3 different LDAP servers hence this approach best suited for my application.
Here is my complete CustomAuthenticationProvider implementation:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final String AT_DOMAIN_COM = "#domain.com";
private static final String SINGLE_SPACE = " ";
#Value("${ldap.url.for.server1}")
private String ldapUrlForServer1; // url set in application.properties
#Value("${ldap.url.for.server2}")
private String ldapUrlForServer2;
#Value("${ldap.url.for.server3}")
private String ldapUrlForServer3;
#Value("${ldap.jndi.context.factory}")
private String ldapContextFactory;
#Value("${ldap.authentication.type}")
private String ldapAuthenticationType; // auth type is "simple"
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username = auth.getName();
String password = auth.getCredentials().toString();
if (isLdapRegisteredUser(username, password)) {
// use the credentials and authenticate against a third-party system
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
} else {
return null;
}
}
boolean isLdapRegisteredUser(String username, String password) {
boolean result = false;
Hashtable<String, String> env = new Hashtable<>();
LdapContext ctx = null;
try {
env.put(Context.INITIAL_CONTEXT_FACTORY, ldapContextFactory);
env.put(Context.SECURITY_AUTHENTICATION, ldapAuthenticationType);
env.put(Context.SECURITY_PRINCIPAL, username + AT_DOMAIN_COM);
env.put(Context.SECURITY_CREDENTIALS, password);
// here I'm checking for 3 different LDAP servers
env.put(Context.PROVIDER_URL, ldapUrlForServer1 + SINGLE_SPACE + ldapUrlForServer2 + SINGLE_SPACE + ldapUrlForServer3);
ctx = new InitialLdapContext(env, null);
if (ctx != null) {
result = true;
String selectedLdapUrl = ctx.getEnvironment().get(Context.PROVIDER_URL).toString();
// do further operations with "ctx" if needed
System.out.println("selected LDAP url is: " + selectedLdapUrl);
System.out.println("Connection Successful!");
}
} catch(NamingException nex) {
nex.printStackTrace();
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (Exception ex) {
}
}
}
return result;
}
#Override
public boolean supports(Class<?> auth) {
return auth.equals(UsernamePasswordAuthenticationToken.class);
}
}
And here is my SecurityConfig implementation:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthProvider;
#Autowired
private CustomAuthenticationFailureHandler customAuthFailureHandler;
#Autowired
private CustomAuthenticationSuccessHandler customAuthSuccessHandler;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/css/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/js/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/sign-in")
.usernameParameter("userid")
.passwordParameter("password")
.successHandler(customAuthSuccessHandler)
.failureHandler(customAuthFailureHandler)
.permitAll()
.and()
.logout()
.clearAuthentication(true)
.logoutSuccessUrl("/login").permitAll()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthProvider);
}
}
Hope this helps :) Happy coding...
I'am trying to implement a Spring Security LDAP authentication using WebSecurityConfigurerAdapter.
So far it works fine, but the problem in my case is that I don't want the username and password of context to be hard coded. It must be the login and password of the user, so my question is how can I build the context and setting of the username and password from the login form?
This is the code I'm working with:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchFilter("(sAMAccountName={0})")
.contextSource(contextSource());
}
#Bean
public BaseLdapPathContextSource contextSource() {
LdapContextSource bean = new LdapContextSource();
bean.setUrl("ldap://10.10.10.10:389");
bean.setBase("DC=myDomaine,DC=com");
//instead of this i want to put here the username and password provided by the user
bean.setUserDn("myDomaine\\username");
bean.setPassword("password");
bean.setPooled(true);
bean.setReferral("follow");
bean.afterPropertiesSet();
return bean;
}
}
Thank you!
Save yourself time by not rewriting your own LdapAuthenticationProvider, there is an existing ActiveDirectoryLdapAuthenticationProvider that will use the received credentials for authentication to LDAP and you can also add a searchFilter if you want to do more (e.g. see if user also belongs to a specific group)
Related docs:
https://docs.spring.io/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ldap-active-directory
https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.html
Sample snippet:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private LdapProperties ldapProperties;
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider authenticationProvider =
new ActiveDirectoryLdapAuthenticationProvider(ldapProperties.getDomain(), ldapProperties.getProviderUrl());
authenticationProvider.setConvertSubErrorCodesToExceptions(true);
authenticationProvider.setUseAuthenticationRequestCredentials(true);
//if you're not happy on the default searchFilter, you can set your own. See https://docs.spring.io/spring-security/site/docs/4.2.18.RELEASE/apidocs/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.html#setSearchFilter-java.lang.String-
authenticationProvider.setSearchFilter("(&(objectClass=user)(cn={1}))");
return authenticationProvider;
}
...
}
Your code should work perfectly fine. The hardcoded username and password is used only to create a bind with the ldap server. The username and password provided in login form is only being authenticated using your code.
I use the following code to perform ldap authentication.
public void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("sAMAccountName={0}").contextSource().url(this.ldapUrl).managerDn(this.managerDn)
.managerPassword(this.managerPassword);
}
Where the manager is the ldap account used to create a bind with the server.
the userDN and password parameter in contextSource is a required parameter. It is like admin username and password for you to be able to acquire or create initial connection to the ldap server.
For you to be able to authenticate the username and password from login form. You can use the ldapTemplate:
#Bean
public BaseLdapPathContextSource contextSource() {
LdapContextSource bean = new LdapContextSource();
bean.setUrl("ldap://10.10.10.10:389");
bean.setBase("DC=myDomaine,DC=com");
//instead of this i want to put here the username and password provided by the user
bean.setUserDn("myDomaine\\username");
bean.setPassword("password");
bean.setPooled(true);
bean.setReferral("follow");
bean.afterPropertiesSet();
return bean;
}
#Bean
public LdapTemplate ldapTemplate() {
LdapTemplate template = new LdapTemplate(contextSource());
return template;
}
Then use this in your service class implementation:
#Service
public class LdapUserServiceImpl implements LdapUserService, BaseLdapNameAware {
#Autowired
protected ContextSource contextSource;
#Autowired
protected LdapTemplate ldapTemplate;
#Override
public boolean authenticate(String userDn, String credentials) {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("sAMAccountName", userDn));
return ldapTemplate.authenticate("", filter.toString(), credentials);
}
}
Then call this service passing the username and password from login form like this:
boolean isAuthenticated = ldapUserService.authenticate(loginForm.getUsername(), loginForm.getPassword());
You can use a customized Authentication Provider to do the authentication.
in your security Configuration, you can auto wire a CustomAuthenticationProvider, which can get the username and password from login form:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
#Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthProvider);
}
}
And Now just implement the CustomAuthenticationProvider
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private static final Logger LOG = LoggerFactory.getLogger(CustomAuthenticationProvider.class);
#Value("${ldap.host}")
String ldapHost;
#Value("${ldap.port}")
String ldapPort;
#Value("${ldap.base-dn}")
String baseDomainName;
#Value("${ldap.domain-prefix}")
String domainPrefix;
#Value("${ldap.read.timeout}")
String timeout;
private static final String DEFAULT_JNDI_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//user and password are from user's input from login form...
String user= authentication.getName();
String password = authentication.getCredentials().toString();
try {
Set<String> roles = authenticate(user, password);
if (CollectionUtils.isEmpty(roles))
return null;
List<GrantedAuthority> authorityList = new ArrayList<>();
for(String role: roles){
authorityList.add(new SimpleGrantedAuthority(role));
}
return new UsernamePasswordAuthenticationToken(user, password, authorityList);
} catch (NamingException ex) {
LOG.info("Naming Exception",ex);
}
return null;
}
public Set<String> authenticate(final String username, final String password) throws NamingException {
InitialLdapContext ctx = null;
NamingEnumeration<SearchResult> results = null;
try {
final Hashtable<String, String> ldapEnvironment = new Hashtable<>();
ldapEnvironment.put(Context.INITIAL_CONTEXT_FACTORY, DEFAULT_JNDI_CONTEXT_FACTORY);
ldapEnvironment.put(Context.PROVIDER_URL, "ldap://" + ldapHost + ":" + ldapPort + "/" + baseDomainName);
ldapEnvironment.put(Context.SECURITY_PROTOCOL, "ssl");
ldapEnvironment.put(Context.SECURITY_CREDENTIALS, password);
ldapEnvironment.put(Context.SECURITY_PRINCIPAL, domainPrefix + '\\' + username);
ldapEnvironment.put("com.sun.jndi.ldap.read.timeout", timeout);
ldapEnvironment.put("com.sun.jndi.ldap.connect.timeout", timeout);
ctx = new InitialLdapContext(ldapEnvironment, null);
final SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
constraints.setReturningAttributes(new String[]{"memberOf"});
constraints.setReturningObjFlag(true);
results = ctx.search("", "(sAMAccountName=" + username + ")", constraints);
if (!results.hasMore()) {
LOG.warn(".authenticate(" + ldapHost + "," + username + "): unable to locate " + username);
return null;
}
final Set<String> adGroups = new TreeSet<>();
final SearchResult entry = results.next();
for (NamingEnumeration valEnum = entry.getAttributes().get("memberOf").getAll(); valEnum.hasMoreElements(); ) {
String dn = (String) valEnum.nextElement();
int i = dn.indexOf(",");
if (i != -1) {
dn = dn.substring(0, i);
}
if (dn.startsWith("CN=")) {
dn = dn.substring("CN=".length());
}
adGroups.add(dn);
}
return adGroups;
}
finally {
try {
if (null != results)
results.close();
}
catch (Throwable ignored) {
}
try {
if (null != ctx)
ctx.close();
}
catch (Throwable ignored) {
}
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class);
}
}
We are using a JWT token as authentication mechanism. When we sign in using Google and Spring social, our /signin endpoint added by Spring social put a JSESSIONID as a cookie. I don't know how to disabled this behavior (that's the question).
As we are using an HTTP header as auth payload we don't need a CSRF protection. We don't want a cookie with a JSESSIONID payload, especially as we don't know the scope and rights of this cookie.
Here is social configuration file:
#Configuration
#EnableSocial
public class SocialConfiguration implements SocialConfigurer {
private final Logger log = LoggerFactory.getLogger(SocialConfiguration.class);
#Inject
private SocialUserConnectionRepository socialUserConnectionRepository;
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
// Google configuration
String googleClientId = environment.getProperty("spring.social.google.clientId");
String googleClientSecret = environment.getProperty("spring.social.google.clientSecret");
if (googleClientId != null && googleClientSecret != null) {
log.debug("Configuring GoogleConnectionFactory");
connectionFactoryConfigurer.addConnectionFactory(
new GoogleConnectionFactory(
googleClientId,
googleClientSecret
)
);
} else {
log.error("Cannot configure GoogleConnectionFactory id or secret null");
}
// Facebook configuration
String facebookClientId = environment.getProperty("spring.social.facebook.clientId");
String facebookClientSecret = environment.getProperty("spring.social.facebook.clientSecret");
if (facebookClientId != null && facebookClientSecret != null) {
log.debug("Configuring FacebookConnectionFactory");
connectionFactoryConfigurer.addConnectionFactory(
new FacebookConnectionFactory(
facebookClientId,
facebookClientSecret
)
);
} else {
log.error("Cannot configure FacebookConnectionFactory id or secret null");
}
// Twitter configuration
String twitterClientId = environment.getProperty("spring.social.twitter.clientId");
String twitterClientSecret = environment.getProperty("spring.social.twitter.clientSecret");
if (twitterClientId != null && twitterClientSecret != null) {
log.debug("Configuring TwitterConnectionFactory");
connectionFactoryConfigurer.addConnectionFactory(
new TwitterConnectionFactory(
twitterClientId,
twitterClientSecret
)
);
} else {
log.error("Cannot configure TwitterConnectionFactory id or secret null");
}
// jhipster-needle-add-social-connection-factory
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new CustomSocialUsersConnectionRepository(socialUserConnectionRepository, connectionFactoryLocator);
}
#Bean
public SignInAdapter signInAdapter() {
return new CustomSignInAdapter();
}
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository, SignInAdapter signInAdapter) throws Exception {
ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, signInAdapter);
providerSignInController.setSignUpUrl("/social/signup");
return providerSignInController;
}
#Bean
public ProviderSignInUtils getProviderSignInUtils(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
return new ProviderSignInUtils(connectionFactoryLocator, usersConnectionRepository);
}
}
And my spring security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private UserDetailsService userDetailsService;
#Inject
private TokenProvider tokenProvider;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Inject
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("/app/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
// TODO unit test the security
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// API part start
... antMatchers here
.and()
.apply(securityConfigurerAdapter());
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
}