I'm trying to implement an authentication and authorization service for an ongoing spring-boot project. I have implemented a JPA based authentication provider and it is working fine. How do I add LDAP authentication provider to the same project and switch between the authentication methods depending on the user authentication type?
Below is my code
#Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UsernamePasswordAuthProvider authProvider;
#Autowired
private LdapAuth ldapAuth;
#Autowired
private LdapAuthenticationpopulator ldapAuthenticationpopulator;
private String ldapUrls = "ldap://localhost:3890";
private String ldapSecurityPrincipal = "cn=admin,dc=mycompany,dc=com";
private String ldapPrincipalPassword = "admin";
private String userDnPattern = "uid={0}";
#Autowired
private UsernamePasswordAuthFilter usernamePasswordAuthFilter;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Order(1)
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url(ldapUrls)
.managerDn(ldapSecurityPrincipal)
.managerPassword(ldapPrincipalPassword)
.and()
.userDnPatterns(userDnPattern)
.ldapAuthoritiesPopulator(ldapAuthenticationpopulator);
}
#Override
#Order(2)
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authProvider);
}
#Override
protected void configure(HttpSecurity http) {
http.addFilterAt(usernamePasswordAuthFilter,
BasicAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Though my LDAP credentials are correct it is not reaching that method.
How do I get the authentication method from DB ex(LDAP, JPA, SSO) for my application and execute the corresponding auth provider method?
I have gone through multiple documents for MultipleAuthenticationProviders but I couldn't find much clarity
Please let me know if there is any possible solution for this.
Thanks in advance
I found one solution to this. I have put up LDAP enabled or JPA auth enabled properties in DB and loading them at run time depending upon the boolean value I'm calling the particular auth method.
Related
Imagine the following (hypothetical) data structure
endpoint | username | password
users admin 123
info george awd
data magnus e4
this means that every endpoint requires different credentials and no one username/password combo can log in to every endpoint. I am looking for a way to make this scalable in our Spring MVC project when adding more endpoints. We could use roles and hardcore this into the config class but the endpoints and login combinations vary for every customer installation
Given the following SecurityConfiguration with LookupAuthenticationService being the class that looks up the username/password data in the database
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String[] ENDPOINT_LIST = {
"/rest/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(ENDPOINT_LIST)
.authenticated()
.and()
.httpBasic();
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected UserDetailsService userDetailsService() {
return new LookupAuthenticationService(passwordEncoder());
}
}
The ideal situation would be if LookupAuthenticationService has access to the request so we know which endpoint to fetch but I guess this is only possible when working with individual Filters
The possibilities I've found so far are:
Add a WebSecurityConfigurerAdapter and multiple UserDetailsServer specific per endpoint -> lots of code
Add a HandlerInterceptor per endpoint -> lots of code
AuthenticationManagerResolver returning a different AuthenticationManager based on pathInfo?
Any input how to best resolve this issue would be appreciated
You can have a table where you map endpoints to rules, like so:
pattern
authority
/users/**
ROLE_ADMIN
/info/**
ROLE_USER
/another/**
ROLE_ANOTHER
And instead of assigning a user to an endpoint, you assign a role to the users. With this in place, you can create an AuthorizationManager which is going to protect your endpoints based on the request path.
#Component
public class AccessRuleAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final AccessRuleRepository rules;
private RequestMatcherDelegatingAuthorizationManager delegate;
public AccessRuleAuthorizationManager(AccessRuleRepository rules) {
this.rules = rules;
}
#Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
return this.delegate.check(authentication, object.getRequest());
}
#EventListener
void applyRules(ApplicationReadyEvent event) {
Builder builder = builder();
for (AccessRule rule : this.rules.findAll()) {
builder.add(
new AntPathRequestMatcher(rule.getPattern()),
AuthorityAuthorizationManager.hasAuthority(rule.getAuthority())
);
}
this.delegate = builder.build();
}
}
And, in your SecurityConfiguration you simply do this:
#Autowired
private AccessRuleAuthorizationManager access;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) ->
authz.anyRequest().access(this.access)
)
.httpBasic(Customizer.withDefaults());
}
I recommend you to take a look at this repository and watch the presentation from the repository's description. The last steps of the presentation was adding the custom AuthorizationManager, and there's a great explanation about it.
I have created a method for persisting user details in the database and i also have a controller which is exposed at the endpoint /register. I wanted to make the /register endpoint available to all. I have used spring security and gave permit all for the /register end point.
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public WebSecurityConfiguration(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
request -> request.antMatchers(HttpMethod.POST,"/register").permitAll()
.anyRequest().authenticated()
);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Can someone please explain or help me out why permitAll is not working in my case. As per the code i have written the /register end point should return the user details but it returns 403. The /register endpoint is a rest endpoint which takes the user details as input and return the user details as output once the detal is persisted to the database.
#Slf4j
#RestController
public class RegistrationController {
private final UserDetailsServiceImpl userDetailsService;
#Autowired
public RegistrationController(UserDetailsServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
#PostMapping(value = "/register")
public ResponseEntity<Users> registerNewUser(#Valid #RequestBody Users users) throws EmailAlreadyExistsException {
Users usersDetails = userDetailsService.processRegistration(users);
log.info("{}, Information: Successfully persisted new user",this.getClass().getSimpleName());
return new ResponseEntity<>(usersDetails,HttpStatus.OK);
}
}
I guess you are calling the url via curl or postman. You must then disable CSRF or use a GET mapping instead.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(
request -> request.antMatchers(HttpMethod.POST,"/register").permitAll()
.anyRequest().authenticated()
);
}
I have an application that authenticates the login against the Microsoft Active Directory, but now I need that if the user is not in the AD of the organization, try to authenticate against an OpenLDAP directory, is it possible with spring-boot in a single application?
How can I indicate in the configuration class that there are two providers to authenticate? Or, do I have to use a handler or similar to perform double authentication?
My code is similar to the following with its own filters and some changes, but the scheme is similar.
Code source: https://medium.com/#dmarko484/spring-boot-active-directory-authentication-5ea04969f220
#Configuration
#EnableWebSecurity
public class WebSecurityConfigAD extends WebSecurityConfigurerAdapter {
#Value("${ad.domain}")
private String AD_DOMAIN;
#Value("${ad.url}")
private String AD_URL;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#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;
}
}
I am also doing the same in my application:
Authenticate against the LDAP
Authenticate against the database.
First of all LDAP server is already set up.
So in application.properties file we have all the information required to connect to the LDAP server and a Boolean field to check whether we want to authenticate against LDAP server or not.
project.ldap.server.protocol=
project.ldap.server.host.name=
project.ldap.server.ip=
project.ldap.server.port=
project.ldap.service.url=
project.ldap.authentication=(false/true)
A service which will perform the authentication against the LDAP server
#Service
public class LDAPAuthenticationConnectorService {
#Value("${project.ldap.server.protocol}")
private String LDAP_SERVER_PROTOCOL;
#Value("${project.ldap.server.ip}")
private String LDAP_SERVER_IP;
#Value("${project.ldap.server.port}")
private int LDAP_SERVER_PORT;
#Value("${project.ldap.service.url}")
private String LDAP_SERVICE_URL;
/**
*
* #param loginDto
* #throws ADAuthenticationException
*/
public String authenticate(LoginDto loginDto) throws ADAuthenticationException{//logic }
Now in FacadeImplementation you can do as following:
public class LoginFacadeImpl implements LoginFacadeInt {
#Value("${project.ldap.authentication}")
private Boolean isProjectLDAPAuthenticationEnabled;
#Override
public UserDto authenticateUser(String userName, String password) {
try {
authenticateViaLDAP(userName, password);
} catch (Exception e1) {
//do authetication against database
UserEntity userEntityForAuthentication =
UserManagementDaoInt.authenticateUser(userName,
AuthenticationUtils.convertPasswordToSha256(password));
}
Hope this helps:)
Let me know:)
I found the solution.
Spring Security supports a wide range of authentication mechanisms. AuthenticationManagerBuilder object allows using multiple built-in authentication provider like In-Memory authentication, LDAP authentication, JDBC based authentication. In addition to its own set of authentication models, Spring Security allows to write your custom authentication mechanism to authenticate, for example, against a secure RESTful or SOAP remote API authentication service.
Link: https://www.baeldung.com/spring-security-multiple-auth-providers
The following example shows how to put two authentication providers, one in a row from another. One: in memory, and other: customAuthentcationProvider.
auth.authenticationProvider(customAuthProvider);
auth.inMemoryAuthentication()
Example:
#EnableWebSecurity
public class MultipleAuthProvidersSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
CustomAuthenticationProvider customAuthProvider;
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(customAuthProvider);
auth.inMemoryAuthentication()
.withUser("memuser")
.password(encoder().encode("pass"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/api/**")
.authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I'm currently developing a React WebApp with a Spring Boot REST API in the backend. I want to use OAuth2 to secure the API (and to improve my knowledge). I have however some questions regarding the correct authentication flow to use.
Since the frontend is using JavaScript I should not use flows that require a client secret.
The only flow that does not require a client secret in Spring is the Implicit Flow. With this flow however, Spring does not support refresh tokens. That means that after some time the user would get automatically logged out and needs to authorize the WebApp again.
Another option I saw was creating a client without a secret and then use the Authorization Code flow. But I have some doubts if this is the right way to go.
So my question basically: Which is the best OAuth2 flow to use with a Javascript frontend, when I don't want the user to be logged out after some time?
WebSecurityConfig
#Configuration
#EnableWebSecurity
#Import(Encoders.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsServiceImpl;
#Autowired
private PasswordEncoder userPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(userPasswordEncoder);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().permitAll()
.and()
.authorizeRequests().antMatchers("/login", "/error**").permitAll()
.anyRequest().authenticated();
}
}
ResourceServerConfig
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource-server-rest-api";
private static final String SECURED_READ_SCOPE = "#oauth2.hasScope('read')";
private static final String SECURED_WRITE_SCOPE = "#oauth2.hasScope('write')";
private static final String SECURED_PATTERN = "/api/**";
#Autowired
private DefaultTokenServices tokenServices;
#Autowired
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenServices)
.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher(SECURED_PATTERN).authorizeRequests().anyRequest().authenticated();
}
}
OAuth2Config
#Configuration
#PropertySource({"classpath:persistence.properties"})
#EnableAuthorizationServer
#Import(WebSecurityConfig.class)
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("dataSource")
private DataSource dataSource;
#Autowired
private UserDetailsService userDetailsServiceImpl;
#Autowired
private PasswordEncoder oauthClientPasswordEncoder;
#Autowired
private AuthenticationManager authenticationManager;
#Bean
public OAuth2AccessDeniedHandler oauthAccessDeniedHandler() {
return new OAuth2AccessDeniedHandler();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
new ClassPathResource("mykeys.jks"),
"mypass".toCharArray());
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mykeys"));
return converter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(oauthClientPasswordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
final TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsServiceImpl);
}
}
The Implicit flow is meant for JavaScript implementations according to the OAuth2 spec
Refresh tokens are not supported with the Implicit flow. The spring implementation is following the Oauth2 spec.
In case of a Javascript client implementations, tokens are stored on the client. When using refresh tokens, the refresh token needs to be persisted on the client in order to obtain a new access token in the future. If this is the case, you might as wel issue a long(er) lasting access token instead of a refresh token. When working client side, there is no advantage in using refresh token.
I'm working on with spring boot security layer to authenticate and authorize the user.Now, i would like to do some sample app using multi http security configuration.I have the scenario like there will be two login pages with different URL mappings("/managementLogin","/othersLogin").
I can understood how to configure multi httpsecurity configs but i need to validate the users from two tables.If the management users loggedIn i need to validate the user from management table through DAO layer using UserDetailsService else if any other users loggedIn i need to validate from other_users table.
Could anybody help me to know how to configure the multi http config and dao layer using UserDetailsService with spring boot security ?
Here is my basic code snippet,
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// for testing authentication purpose using inMemory db
/*
* auth.inMemoryAuthentication().withUser("user").password("user").roles
* ("USER").and().withUser("admin") .password("admin").roles("ADMIN");
*/
// Dao based authentication
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home").permitAll();
http.authorizeRequests().antMatchers("/rest/**").authenticated();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutSuccessUrl("/");
// CSRF tokens handling
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/registerUser","/register.html");
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
TIA..,
Implement a custom UserDetailsService like this:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDaoTableOne userDaoTableOne;
#Autowired
private UserDaoTableTwo userDaoTableTwo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = userDaoTableOne.find(username);
if(user == null){
user = userDaoTableTwo.find(username);
}
if (user == null) {
throw new UsernameNotFoundException(String.format("Username '%s' not found", username));
}
return user;
}
}
Implement two DaoAuthenticationProvider with his own UserDetailsService and inject both providers to the authenticationManager.
I don't know what is the requisite for two distinct login endpoints but at first I think is a bad idea.
You can create different Authentication objects an let the AuthenticationManager choose the correct AuthenticationProvider based in the supports method.
Indeed you will need to use, two user detail services. But, that wont be enough. I suggest you to create another ApplicationSecurity2 class with different order.
Spring security is built on an ordered list of filter chains.
see the answer given here by Dave Sayer. Then you can handle different urls, as you want.
in my case I checked into two repositories, Below an exemple that I use:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AbstractUser user;
try {
user = clientRepository.findByUsername(username);
}
catch (Exception userException) {
try {
user = adminRepository.findByUsername(username);
}
catch (Exception adminException) {
throw new UsernameNotFoundException("No user present with username : " + username);
}
}
return user;
}
I have to handle around same issue , i have autowired httprequest class in userdetail service and get request params type and drive my logic based on that.
you can directly solve the issue as the recommended solutions, but you can create a simple trick to define two different UserDetailsService as here I have two user one as a normal user and another as an editor :
editor
#Log4j2
#RequiredArgsConstructor
#Service
public class EditorService implements UserDetailsService {
private final EditorRepository editorRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username == null || "".equals(username)){
throw new UsernameNotFoundException("null value");
}
Optional<Editor> editor = editorRepository.findByUsername(username);
if(editor.isPresent()){
log.info("created under editor service: " + editor.get());
return editor.get();
}
throw new UsernameNotFoundException("does not exists");
}
}
user
#Log4j2
#RequiredArgsConstructor
#Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if(username == null || "".equals(username)){
throw new UsernameNotFoundException("null");
}
Optional<User> user = userRepository.findByUsername(username);
if(user.isPresent()){
log.info("cretaed under User service : " + user.get());
return user.get();
}
throw new UsernameNotFoundException("does not exists");
}
}
then on the configurations side, we can use of spring order mechanism :
user config :
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(1)
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/user/**")
.and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userService).passwordEncoder(passwordEncoder());
}
}
Editor config :
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
public class EditorWebSecurityConfig extends WebSecurityConfigurerAdapter {
private final EditorService editorService;
#Lazy
private final PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http // all other requests handled here
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.editorService).passwordEncoder(passwordEncoder);
}
}