Combine form based and oauth2 based login providers in a spring boot 2 + spring security 5 app - spring-boot

I currently have a working setup for a form based login in my app using spring boot 2.0.6 and spring security 5:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated().and().formLogin()
.loginPage("/login").permitAll().and().logout().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11);
}
}
There are also examples of adding support for oauth2 with the following spring dependency:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
and a helpful blog post that does well to show how you can use it:
https://spring.io/blog/2018/03/06/using-spring-security-5-to-integrate-with-oauth-2-secured-services-such-as-facebook-and-github
How do I extend my current security config to add support for Oauth2 logins such as facebook?
Thank you kindly,
Jason

Related

Spring Boot 3 BeanCreationException: Error creating bean with name authorizationEndpoint - ClassNotFoundException: javax.servlet.ServletException

I have a simple-as-possible OAuth2 JWT Spring Boot 2.7.3 project that works perfectly. It's easy to setup and run as described in the README from Github repository.
Besides Spring Security, the project uses spring-security-oauth2:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Then defines some beans:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Value("${jwt.secret}")
private String jwtSecret;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setSigningKey(jwtSecret);
return tokenConverter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
And an Authorization Server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${jwt.duration}")
private Integer jwtDuration;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private JwtAccessTokenConverter accessTokenConverter;
#Autowired
private JwtTokenStore tokenStore;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.scopes("read", "write")
.authorizedGrantTypes("password")
.accessTokenValiditySeconds(jwtDuration);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter);
}
}
And a Resource Server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Value("${cors.origins}")
private String corsOrigins;
#Autowired
private Environment env;
#Autowired
private JwtTokenStore tokenStore;
private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" };
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
// H2
if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
http.headers().frameOptions().disable();
}
http.authorizeRequests()
.antMatchers(PUBLIC).permitAll()
.anyRequest().authenticated();
http.cors().configurationSource(corsConfigurationSource());
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
String[] origins = corsOrigins.split(",");
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOriginPatterns(Arrays.asList(origins));
corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH"));
corsConfig.setAllowCredentials(true);
corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
#Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> bean
= new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource()));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Now I'm trying to upgrade it to Spring Boot 3.0.0-SNAPSHOT, but the project is throwing BeanCreationException: Error creating bean with name 'authorizationEndpoint'. The stack trace shows it's caused by ClassNotFoundException: javax.servlet.ServletException.
How to fix that?
The problem is that you are using an incompatible version of spring cloud (Hoxton.SR8). It is only compatible with spring boot 2.3.x and 2.2.x (see https://spring.io/blog/2020/08/28/spring-cloud-hoxton-sr8-has-been-released). You need to upgrade spring cloud as well.
(Spring boot 3 uses the new Jakarta APIs so jakarta.servlet.ServletException is only available and downloaded by maven after you upgraded Spring boot. Your incompatible spring cloud version expects the old javax.servlet.ServletException class, but is not there anymore. Therefore this ClassNotFoundException.)
The dependency:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Has reached its End of Life some time ago. Therefore, it won't be compatible with Spring Boot 3. The project has been replaced by the OAuth2 support provided by Spring Security (client and resource server) and Spring Authorization Server.
When I first ran the application(converted to gradle), I got the exception java.lang.ClassNotFoundException: javax.servlet.Filter. It went away once I added the javax-servlet-api dependency. Can you also try adding the below dependency?
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>

Make DaoAuthenticationProvider Work with the AuthenticationManager after WebSecurityConfigurerAdapter Deprecation

I am creating a complete user login and registration Backend system with Email Verification and usage of PostgreSQL to store the user's credentials. I've come to a point where I am having problems at the security layer. To be more specific I am having the following code which since WebSecurityConfigurerAdapter deprecation, I want to change:
OLD VERSION BEFORE DEPRECATION
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(applicationUserService);
return provider;
}
I've searched this question and found that AuthenticationManagerBuilder can now be accessed as follows:
NEWEST VERSION OF AUTHENTICATION MANAGER
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
My problem is that I can't find a way to inject my daoAuthenticationProvider to the newest method of AuthenticationManager. Any proposals???
You should not need the AuthenticationConfiguration for that, you could just create your own bean, like so:
#Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userDetailsService);
return new ProviderManager(provider);
}
Adding a custom authentication provider is configured in the SecurityFilterChain bean. Although looking at the given code, standard DAO authentication would automatically be added with http.formLogin() without the need for an AuthenticationProvider.
#Bean
public SecurityFilterChain filterChain(DaoAuthenticationProvider daoAuthenticationProvider) throws Exception
{
http.authenticationProvider(daoAuthenticationProvider);
return http.build();
}
See also https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

Integration with Active Directory using spring boot ldap

i am new to concept of active directory, so to integrate active directory with ldap i am using spring boot framework i.e using spring security with ldap.
So based on same reference from stackoverflow i am not able to managed authentication with active directory.
Below is my sample code :
#Configuration
#EnableGlobalAuthentication
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin();
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = new ActiveDirectoryLdapAuthenticationProvider(
"xyzabcd.org", "ldap://192.168.100.161:389/");
return activeDirectoryLdapAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=Users").contextSource(contextSource());
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(Arrays.asList("ldap://192.168.100.161:389/"),
"DC=xyzabcd.org,DC=org");
}
}
with this i am getting below error
Your login attempt was not successful, try again.
Reason: Bad credentials
so guys please help me out with this problem
Thanks

Authentication provider per url pattern - Spring Boot

I faced problem when configuring different auth providers per url pattern using Spring Boot security. I am trying to configure security in Spring Boot app and want to have swagger behind basic auth and all API is secured only by token. I have it almost working, but noticed that API except the fact that it is secured by token which is verified by IDAuthProvider class it also is secured by basic auth. I do not want that and also noticed that if I removed line:
sessionCreationPolicy(SessionCreationPolicy.STATELESS).
it seems to be working correctly, but still header Basic {token} is being added in request which is something I do not want...
Do you know how can I configure it to make all swagger stuff secured by basic auth and API stuff secured by token?
My configuration looks like below:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Configuration
#Order(1)
public class SwaggerSecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationProvider userPassAuthProvider;
#Autowired
SwaggerSecurityConfig(UserPassAuthProvider userPassAuthProvider) {
this.userPassAuthProvider = userPassAuthProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/swagger**").
authorizeRequests().
antMatchers("/swagger**").authenticated().
and().httpBasic().and().csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(userPassAuthProvider);
}
}
#Configuration
#Order(2)
public class APISecurityConfig extends WebSecurityConfigurerAdapter {
private final AuthenticationProvider idAuthProvider;
#Autowired
APISecurityConfig(IDAuthProvider idAuthProvider) {
this.idAuthProvider = idAuthProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/v1/**").
authorizeRequests().anyRequest().authenticated().
and().
addFilterBefore(idpAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class).sessionManagement().
and().
csrf().disable();
}
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(idAuthProvider);
}
IDPAuthenticationFilter idpAuthenticationFilter(AuthenticationManager auth) {
return new IDPAuthenticationFilter(auth, new OrRequestMatcher(new AntPathRequestMatcher(ApiRouter.API_PATH + "/**", HttpMethod.GET.toString()), new AntPathRequestMatcher(ApiRouter.API_PATH + "/**", HttpMethod.POST.toString()), new AntPathRequestMatcher(ApiRouter.API_PATH + "/**", HttpMethod.DELETE.toString()), new AntPathRequestMatcher("/swagger**", HttpMethod.GET.toString())));
}
}
}

How to combine two different configuration files in Spring boot?

I have multiproject Spring application.
Project A - responsible for LDAP authentication
Project B - responsible for Database authentication
Project MAIN - can use both of them or one of them.
If we use only Project A - we have LDAP auth
If we use only Project B - we have JDBC auth
If we use both of them - first goes LDAP auth, if it failures, then goes JDBC auth. And if Project B is included, it adds some filters
Project MAIN does not have #Configuration file, but Projects A and B has it.
Project A #Configuration
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**адрес сервера LDAP*/
#Value("${ldap.server}")
private String ldapServer;
/**номер порта LDAP сервера*/
#Value("${ldap.port}")
private int ldapPort;
/**домен для LDAP*/
#Value("${ldap.suffix}")
private String suffix;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(adAuthProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().disable();
}
/**провайдер для аутентификации через LDAP*/
#Bean
public ActiveDirectoryLdapAuthenticationProvider adAuthProvider() {
String ldapUrl = String.format("ldap://%s:%s", ldapServer, ldapPort);
ActiveDirectoryLdapAuthenticationProvider adAuthProvider = new
ActiveDirectoryLdapAuthenticationProvider(suffix, ldapUrl);
adAuthProvider.setConvertSubErrorCodesToExceptions(true);
adAuthProvider.setUseAuthenticationRequestCredentials(true);
return adAuthProvider;
}
}
and Project B Configuration file.
#Configuration
#EnableWebSecurity
public class ECommonConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jdbcAuthProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().disable();
http.addFilterAt(ldapAuthenticationFilter(), LDAPAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/**").access("#requestAuthorization.checkRequestPermissions(authentication, request)");
}
/**провайдер для аутентификации через базу данных*/
#Bean
public DaoAuthenticationProvider jdbcAuthProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**бин для шифрования паролей*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**бин для фильтра проверки наличия LDAP-пользователя в базе данных*/
#Bean
public LDAPAuthenticationFilter ldapAuthenticationFilter() throws Exception {
return new LDAPAuthenticationFilter(authenticationManager());
}
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**бин для инициализации базы данных по умолчанию - описание параметров подключения к БД в файле application.yml*/
#Bean
public DataSource dataSource() {
return datasourceConnectionManager().getDataSource("test");
}
/**бин создания менеджера подключения к нескольким базам данных*/
#Bean
public DatasourceConnectionManager datasourceConnectionManager() {
return new DatasourceConnectionManager();
}
}
I need these two configurations works together or only one oh them
To combine this 2 ways of authentication you can create a custom authentication provider ( more details here: https://www.baeldung.com/spring-security-authentication-provider )
The implementation of the auth provider would look something like this:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private ActiveDirectoryLdapAuthenticationProvider ldapAuthenticationProvider;
private DaoAuthenticationProvider daoAuthenticationProvider;
// env variable to help you choose which auth provider should be enabled
#Value("${ldap.enabled}")
private int ldapEnabled;
// env variable to help you choose which auth provider should be enabled
#Value("${daoAuth.enabled}")
private int daoAuthEnabled;
#Autowired
public CustomAuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider ldapAuthenticationProvider, DaoAuthenticationProvider daoAuthenticationProvider) {
this.ldapAuthenticationProvider = ldapAuthenticationProvider;
this.daoAuthenticationProvider = daoAuthenticationProvider;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication);
// if both enabled then first try with ldap, if not successful try with dao
if (ldapEnabled && daoAuthEnabled ) {
Authentication authenticate = ldapAuthenticationManager.authenticate(authentication);
if(!authenticate.isAuthenticated()) {
authenticate = ldapAuthenticationManager.authenticate(authentication);
}
return authenticate;
}
// if only ldap enabled
if(ldapEnabled) {
return ldapAuthenticationManager.authenticate(authentication);
}
// if only dao enabled
return daoAuthenticationProvider.authenticate(authentication);
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
You can use Spring profiling for this. Just Add #Profile annotation along with name on the configuration class as shown below.
Configuration for ProjectA
#Profile("ProjectA")
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
And Configuration for ProjectB
#Profile("ProjectB")
#Configuration
#EnableWebSecurity
public class ECommonConfig extends WebSecurityConfigurerAdapter {
...
Then at the time of execution of application you can specify active profile by passing following parameter to java.
#In case of need of only ProjectA then
-Dspring.profiles.active=ProjectA
#In case of need of only ProjectB then
-Dspring.profiles.active=ProjectB
#In case of need of both projects then
-Dspring.profiles.active=ProjectA,ProjectB
Same thing you can define in application.properties file with required profile
spring.profiles.active=ProjectA,ProjectB
This way you can dynamically decide which Project configuration should be included.

Resources