My application is structured with Java and Spring Security. I have two ways to log in, one is using windows log in (aka active directory), the other is using a password created by an admin user. Both of these are working when I run locally through IntelliJ but when I deploy the app to the server, it only lets people using windows login credentials log in. If you try to log in with a custom made login on the live site, it just refreshes the login page and removes the entered username and password. I am not getting any errors. Also, when a user admin creates the custom log in, it is stored in a sql table, with the password hashed. So the create function works/user exists, just logging in is not working. Any idea what might be the cause... Not sure if I should be looking at the code or the server connection. I use Tomcat and IIS to host this site.
Web Config File
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${ad.hg.url}")
private String AD_HG_URL;
#Value("${ad.hp.nt.url}")
private String AD_HP_NT_URL;
#Autowired
DBAuthorizationFetcher dbAuthorizationFetcher;
#Autowired
ManualUserDetailsService manualUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/specialSplit/**");
http.authorizeRequests()
.antMatchers("/css/**","/js/**","/images/**","/login","/accessDenied","/loginFailed","/changePassword","/resetPassword").permitAll()
.antMatchers("/newClient","/callLogs/**","/addClient","/saveClient","/delete/**","/save/**","/specialSplit/**").hasRole("OLIDB_ADMIN")
.antMatchers("/admin","/toggle/user/**").hasRole("USER_ADMIN")
.anyRequest().hasRole("OLIDB_USER").and()
.formLogin().loginPage("/login").failureHandler(new CustomAuthenticationFailureHandler()).successForwardUrl("/")
.and().exceptionHandling().accessDeniedPage("/accessDenied")
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/");
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
//authManagerBuilder.authenticationProvider(databaseAuthenticationProvider);
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider("HEFFGROUP.COM",AD_HP_NT_URL));
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider("HG",AD_HG_URL));
authManagerBuilder.authenticationProvider(manualAuthenticationProvider());
}
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider(String domain,String url) {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(domain, url);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(dbAuthorizationFetcher);
return provider;
}
public DaoAuthenticationProvider manualAuthenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(manualUserDetailsService);
authProvider.setPasswordEncoder(new BCryptPasswordEncoder(11));
return authProvider;
}
}
Related
I have a spring boot resource server that authenticates the user by the accessToken extracted from a cookie. The accessToken is retrieved from Cognito UserPool in a react FE and written to a cookie. It seems spring managed to authenticate the user and I can see username in SecurityContextHolder.getContext().authentication.name. I need to retrieve the rest of user attributes, like email. Most of the solutions I looked up say the SecurityContextHolder.getContext().authentication.principal should contain all attributes I need. Its a string in my case and I can't cast it to any User object. Even SecurityContextHolder.getContext().authentication.details is null.
I have user-info-uri defined in my application-properties security.oauth2.resource.user-info-uri. I feel I missing something that causes user attributes to be missing from the authentication context.
This is my resource server security configuration:
#Configuration
#EnableResourceServer
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends ResourceServerConfigurerAdapter {
private final ResourceServerProperties resource;
public SpringSecurityConfig(ResourceServerProperties resource) {
this.resource = resource;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenExtractor(new CustomExtractor());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests();
}
// Enabling Cognito Converter
#Bean
public TokenStore jwkTokenStore() {
return new JwkTokenStore(
Collections.singletonList(resource.getJwk().getKeySetUri()),
new CognitoAccessTokenConverter(),
null);
}
}
Google chrome [EDIT] Version 80.0.3987.162 (Official Build) (64-bit)[/EDIT] waits for a response indefinitely when trying to login to my spring security web app.
I managed to produce this error with a simple demo app using spring initialzr with these specs:
spring boot 2.2.6
spring web
spring boot devtools
spring security
The only thing I added, was a simple WebSecurityConfigurerAdapter
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService userDetailsService;
#Autowired
public WebSecurityConfig(final MyUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.logout().permitAll();
}
#Autowired
public void globalSecurityConfiguration(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth
.userDetailsService(userDetailsService)
.passwordEncoder(encoder);
}
}
and a UserDetailsService
#Service
public class MyUserDetailsService implements UserDetailsService {
// #Autowired
// private UserRepository userRepository;
public MyUserDetailsService() {
}
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
return getUserTestStyle();
}
private UserDetails getUserTestStyle() {
return new MyUser("admin", "SimplePassword#");
}
}
I see that in devtools, the login-POST-Request stays on Pending, even after the server has fully processed the login-request (and confirms in DEBUG-log that authentication was successful).
Things I observed and confirmed:
the server authentication is successful (even though the browser does not redirect) (i can access secured URLs as soon as the server processed the login-request)
this happens only on the very first login-attempt when Chrome has been freshly opened (but when doing so, the issue happens every time)
the problem does not occur, when I change the password on the server side, and enter the changed password on the client
the problem has been gone after I completely cleared all my browser data (CTRL+SHIFT+DEL)
this problem is chrome-specific, I cannot reproduce this with Firefox or Microsoft Edge
Hi I have a Rest WS using WebSecurityConfigurerAdapter to implement HTTP Basic auth.
The password is allowed to be updated and I need to let the WS to pick up updated password without restarting server
Following are the codes:
SecurityConfig
// init a user with credentials admin/password
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//disable csrf
.csrf().disable()
//authentic all requests
.authorizeRequests().anyRequest().authenticated().and().httpBasic()
//disable session
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager());
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
Properties users = new Properties();
users.put("admin", "password,USER,enabled");
return new InMemoryUserDetailsManager(users);
}
}
The controller that will update password
#RestController
public class someController{
#Autowired
public InMemoryUserDetailsManager inMemoryUserDetailsManager;
// update password from password -> pass
#RequestMapping(...)
public updatePass(){
ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
grantedAuthoritiesList.add(new SimpleGrantedAuthority("USER"));
this.inMemoryUserDetailsManager.updateUser(new User("admin", "pass", grantedAuthoritiesList));
}
// another way that also doesn’t work
#RequestMapping(...)
public newUpdate(){
ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
grantedAuthoritiesList.add(new SimpleGrantedAuthority("USER"));
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("admin", "pass",
grantedAuthoritiesList);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
}
}
After calling updatePass() with credential admin/password for the first time, I can see that the password has been updated to "pass" in debugger
I assume that if I'm to call updatePass() again, I should use admin/pass. However it turned out to be still using the old admin/password.
Sources I referred to when writing this code source1 source2
*I'm using Advance Rest Client to make the calls
When you update the password, you have to set the UserDetails in springSecurityContext object if the user is authenticated.
instead of using SecurityContext, I overwrote function loadUserByUsername of interface UserDetailsService to let spring security always pick up the latest pwd from DB.
I'd like to prevent people from accessing my application (Angular 7 frontend, Spring Boot backend) when they are not logged in using Spring-Boot-Security. I authenticate my user via ldap and would like to set roles based on database entries, but let's get to this step by step. So what I did now is replacing the default Spring-Boot-Security-login (via the generated password) with my ldap-configuration (but still using the default login-page). I got the following code for that:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/login")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
http.formLogin().defaultSuccessUrl("/", true);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN, AD_URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Now by default when including the spring-boot-starter-security artifact, everyone is redirected to the /login-page when trying to access any page. Sadly since I overwrote that config with my own that is not the case any more. How can I let spring do this again (also with the frontend-pages, which were prevented from accessing too)?
I know almost nothing about LDAP and even less about spring security but I am trying to configure a spring boot app to authenticate against an ldap instance and am stuck.
I was given the ldap server name at adldap.company.com and base dn of dc=ad,dc=company,dc=com
I have some python code that does a simple bind and works.
LDAP_USERNAME = 'username#ad.company.com'
LDAP_PASSWORD = 'password'
base_dn = 'dc=ad,dc=company,dc=com' # not used for bind I guess, only search
try:
ldap_client = ldap.initialize('ldap://adldap.company.com')
ldap_client.set_option(ldap.OPT_REFERRALS,0)
ldap_client.simple_bind_s(LDAP_USERNAME, LDAP_PASSWORD)
except ldap.INVALID_CREDENTIALS as e:
ldap_client.unbind()
return 'Wrong username and password: %s' % e
except ldap.SERVER_DOWN:
return 'AD server not available'
If I run this code, it seems to successfully bind as "username#ad.company.com" with password "password".
I also have a WebSecurityConfig class that I think should be handling auth:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secure")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0}")
.contextSource()
.url("ldap://adldap.company.com");
//.url("ldap://adldap.company.com/dc=ad,dc=company,dc=com");
}
}
When I go to /secure in the app, I get a basic auth pop up but then anything I try entering gets me a 401 Unauthorized. I have tried "username#ad.company.com", without the domain, putting that stuff in the userDnPatterns like {0}#adldap.company.com and a bunch of other things. I have tried using different URLs with the base dn in it or not. Nothing seems to work. What am I missing?
Also, is this the right way to auth users? I've read about both bind authentication and something about binding and searching but the server doesn't allow anonyous binds so I guess I would need some kind of "app user" that could bind and do the searches, right? Is that "better"?
Active Directory has its own non-standard syntax for user authentication, different from the usual LDAP DN binding.
Spring Security provides a specialized AuthenticationProvider for Active Directory.
Try this :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/secure")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("adldap.company.com", "ldap://adldap.company.com");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
Long story short, the problem is that Microsoft Active Directory LDAP is not "Vanilla" LDAP and thus you need to connect to it differently.
The working solution is here: https://medium.com/#dmarko484/spring-boot-active-directory-authentication-5ea04969f220