Spring Security Java Config Multiple Group Search Base - spring-security

I am using Spring Security 3.2.5 with Java config and LDAP authentication/authorization.
We have a requirement to search for groups in two separate trees in LDAP.
ou=groups
and
ou=Groups,ou=webapps,ou=Applications
I have searched and have been unable to find any information on this topic.
This is my current code which is working fine:
#Autowired
public void configureGlobal(UserDetailsContextMapper userDetailsContextMapper, LdapContextSource contextSource, AuthenticationManagerBuilder builder) throws Exception {
builder
.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper)
.contextSource(contextSource)
.userSearchFilter("cn={0}")
.userSearchBase("ou=Users")
.groupSearchBase("ou=groups");
}
I want to do something like this:
builder
.ldapAuthentication()
.userDetailsContextMapper(userDetailsContextMapper)
.contextSource(contextSource)
.userSearchFilter("cn={0}")
.userSearchBase("ou=Users")
.groupSearchBase("ou=groups")
.groupSearchBase("ou=Groups,ou=webapps,ou=Applications");
Which understandably does not work.
Anyone have any pointers on where to start?

My solution was to create an implementation of org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator which can call multiple instances of LdapAuthoritiesPopulator. Then create one LdapAuthoritiesPopulatorfor each 'groupSearchBase' that I wanted to query.
#Autowired
public void configureGlobal(UserDetailsContextMapper userDetailsContextMapper, LdapContextSource contextSource, AuthenticationManagerBuilder builder) throws Exception {
MultipleLdapAuthoritiesPopulator multipleLdapAuthoritiesPopulator = new MultipleLdapAuthoritiesPopulator(
new DefaultLdapAuthoritiesPopulator(contextSource, "ou=Groups,ou=webapps,ou=Applications"),
new DefaultLdapAuthoritiesPopulator(contextSource, "ou=groups"));
builder
.ldapAuthentication()
.ldapAuthoritiesPopulator(multipleLdapAuthoritiesPopulator)
.userDetailsContextMapper(userDetailsContextMapper)
.contextSource(contextSource)
.userSearchFilter("cn={0}")
.userSearchBase("ou=Users");
}
class MultipleLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
private List<LdapAuthoritiesPopulator> authoritiesPopulators;
public MultipleLdapAuthoritiesPopulator(LdapAuthoritiesPopulator...authoritiesPopulators) {
this.authoritiesPopulators = asList(authoritiesPopulators);
}
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
List<GrantedAuthority> grantedAuthorities = authoritiesPopulators.stream()
.map(authPopulator -> authPopulator.getGrantedAuthorities(userData, username))
.flatMap(Collection::stream)
.collect(Collectors.toList());
return grantedAuthorities;
}
}

Related

Securing Spring Boot with vaadin

Instead of using InMemory Auth I am trying to authenticate with BD user, so a created class user and roles, and added this code to this class
#EnableWebSecurity
#Configuration
public class SecurityConfig extends VaadinWebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetails customUserDetails;
#Autowired
CustomAuthenticationProvider customAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
// Set default security policy that permits Vaadin internal requests and
// denies all other
super.configure(http);
setLoginView(http, LoginView.class, "/logout");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication().withUser("user").password("{noop}userpass").roles("USER");
auth.userDetailsService(customUserDetails).passwordEncoder(passwordEncoder());
}
#Bean
public DaoAuthenticationProvider createDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(customUserDetails);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
}
But when i try to authenticate it does not accept
There is more than one way to do this, but usually it goes roughly like this. The relevant pieces, the complete solution is too lengthy for SO. There should not be anything Vaadin specific, but just follow Spring documentation on the matter.
#EnableWebSecurity
#Configuration
public class SecurityConfig extends VaadinWebSecurityConfigurerAdapter {
...
private final UserDetailsService userDetailsService;
#Autowired
public SecurityConfig (UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
...
}
And then implement UserDetailsService according to Spring spec.
#Service
#Primary
public class UserDetailsServiceImpl implements UserDetailsService {
...
#Autowired
private UserRepository repository; // or what ever you have named it ...
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// implement this according to your actual implementation of user database
}
}

Spring Security: hasAuthority,hasRole and PreAuthorize does not work

I am trying to test my web api thats secured using the standard Spring Security annotations and methods.
I reviewed all the options on the site, nothing helped, here is the code. Without roles, everything works fine.I have been suffering for several days with this problem.I will be grateful for the help, thanks.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImp userDetailsService;
#Autowired
JwtFilter jwtFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST,"/authenticate").permitAll()
.antMatchers(HttpMethod.GET,"/userData").permitAll()
.antMatchers(HttpMethod.GET,"/allUsers").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Controller class
#RestController
public class AuthenticationController {
#Autowired
public AuthenticationManager authenticationManager;
#Autowired
private UserDetailsServiceImp userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Autowired
private UserRepository userRepository;
#RequestMapping(value = "/userData", method = RequestMethod.GET)
public String hello(){
return "Hello new User";
}
#RequestMapping(value = "/allUsers", method = RequestMethod.GET)
public List<UserD> findAll(){
return userRepository.findAll();
}
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest jwtRequest) throws Exception{
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jwtRequest.getName(), jwtRequest.getPassword()));
}catch(BadCredentialsException e){
throw new Exception("Incorrect username and password", e);
}
final UserD userD = (UserD)userDetailsService.loadUserByUsername(jwtRequest.getName());
final String token = jwtUtil.generateToken(userD.getName(), userD.getRole());
Map<Object, Object> model = new HashMap<>();
model.put("username", jwtRequest.getName());
model.put("token", token);
return ResponseEntity.ok(model);
}
}
If antMatchers(HttpMethod.GET,"/allUsers").permitAll()
,then it returns users as it should
UserDetailsServiceImp
#Service
public class UserDetailsServiceImp implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByName(username).get(0);
}
}
and User data with the addition of roles
#Component
public class DataInitializer implements CommandLineRunner {
#Autowired
UserRepository userRepository;
#Autowired
PasswordEncoder passwordEncoder;
#Override
public void run(String... args) throws Exception {
UserD user = new UserD();
user.setName("user");
user.setPassword(passwordEncoder.encode("password"));
user.setRole("ROLE_USER");
userRepository.save(user);
UserD admin = new UserD();
admin.setName("admin");
admin.setPassword(passwordEncoder.encode("password"));
admin.setRole("ROLE_ADMIN");
userRepository.save(admin);
}
}
this is what the database returns
I want to clarify something first, which may help you to pinpoint the issue:
does your UserDetailsServiceImp retrieve username and role from database or LDAP or other repository?
what role is retrieved from database or LDAP or other repository?
does it have prefix "ROLE_" already or not?
if the role retrieved from database is "ADMIN", you shall not add ROLE_ by yourself when you call hasAuthority().
.antMatchers(HttpMethod.GET,"/allUsers").hasAuthority("ADMIN")
And if it is not the case, enable the debug in the log, to see what is exactly going on with the /allUsers request.
UPDATE:
And one thing i suspected was your implementation of UserDetailsServiceImp.
You have to make sure the role was set into user details.
in your implementation, it seems that you queried out from DB directly, not sure if you set the role into the userDetails.
return userRepository.findByName(username).get(0);
Here is something it should happen:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//assume, there is only one user with the username
UserD user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
List<GrantedAuthority> roles = new ArrayList<>();
//assume, there is only one role for the user
roles.add(new SimpleGrantedAuthority(user.getRole()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
roles);
}

Spring EnableAuthorizationServer with custom AuthenticationManager

I am setting up an OAuth2 + OpenID connect server using Spring security. I have been trying to use the automatic /oauth/token & /oauth/authorize endpoints that are defined when you use the #EnableAuthorizationServer annotation on a class.
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
In the same class, I have autowired an AuthenticationManager to use in configuring the AuthorizationServerEndpointsConfigurer. I have debugged & confirmed that the correct bean is being autowired.
#Autowired
private AuthenticationManager authMan;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception{
endpoints
.tokenStore(tokenStore())
.userApprovalHandler(userApprovalHandler())
.authenticationManager(authMan);
}
The problem is, there are two WebSecurityConfigurers being created, the one I defined and what appears to be the default WebSecurityConfigurer. Here is part of the one I defined:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsSrvc detailsSrvc;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(detailsSrvc);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
#Bean(name="myAuthenticationManager")
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
Unfortunately, the default is being called when I navigate to localhost:8080/outh/token with my browser. I can tell because my custom UserDetailsService is not being used during the authentication, and because I put a breakpoint on the getWebSecurityConfigurers method in org.springframework.security.config.annotation.web.configuration.AutowiredWebSecurityConfigurersIgnoreParents:
#SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
The beansOfType map has two entries, but only if I have a class with the #EnableAuthorizationServer annotation. (Only 1 if I comment out annotation)
How do I get my AuthorizationServerConfigurerAdapter (or whatever is actually processing the requests to /oauth/token) to use the WebSecurityConfigurer defined in my WebSecurityConfigurerAdapter? I believe I can get around this issue by defining my own endpoints, and maybe that's the only solution, but I was hoping to utilize the default endpoints.

Using properties file for user credentials with java configuration in Spring security

In XML configuration it's possible to refer to a property file defining user credentials. For example:
<security:user-service id="userDetailsService" properties="classpath:users.properties"/>
However in java configuration there is no default alternative available (that I'm aware of) but writing your own implementation like:
#Bean
public UserDetailsService userDetailsService() {
Properties properties = new Properties();
try {
File file = ResourceUtils.getFile("classpath:users.properties");
properties.load(new FileInputStream(file));
return new InMemoryUserDetailsManager(properties);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
I was wondering if it would make a nice addition to have this provided by the spring security framework out of the box. Something like :
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication("classpath:user.properties")
...
or
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUserCredentialsFile("classpath:user.properties")
...
I'm not sure if adding this improvement to Spring Security makes sense. As you had outlined, the equivalent Java Configuration would be:
#Bean
public UserDetailsService userDetailsService() throws Exception {
Properties users = PropertiesLoaderUtils.loadAllProperties("users.properties");
return new InMemoryUserDetailsManager(users);
}
This looks pretty straight forward for the user. What do you think?

How to secure Apache Camel rest endpoint with Spring Security and OAuth2

I'm working on Spring Boot application with configured SSO/OAuth2 security.
Authentication works fine for my rest controllers and now I need to secure my Apache Camel route with a rest endpoint.
As I understand there are several ways how to do it:
By adding auth processor to my route
By adding policy (SpringSecurityAuthorizationPolicy) to my route
By handlers option to jetty endpoint
I'm trying to do it by adding new auth processor to my rest endpoint but I stuck on this exception:
org.springframework.security.oauth2.common.exceptions.OAuth2Exception:
No AuthenticationProvider found for
org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
During debugging I see that org.springframework.security.authentication.ProviderManager.getProviders() contains only one provider AnonymousAuthenticationProvider so probably I have to register appropriate provider...
Can someone help me to find the right way to solve this problem please?
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().permitAll();
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${oauth2.token.endpoint}")
private String tokenEndpoint;
#Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId("clientId");
tokenServices.setClientSecret("clientSecret");
tokenServices.setCheckTokenEndpointUrl(tokenEndpoint);
return tokenServices;
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
}
}
#Configuration
public class EmbeddedServerRoute {
#Bean
public RoutesBuilder embeddedServer() {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
restConfiguration().component("jetty").port("8081").bindingMode(RestBindingMode.json);
}
};
}
}
#Component
public class RestTestRoute extends RouteBuilder {
#Autowired
private AuthProcessor authProcessor;
#Override
public void configure() throws Exception {
from("rest:get:/test").process(authProcessor).to("mock:end").end();
}
}
#Component
public class AuthProcessor implements Processor {
#Autowired
private AuthenticationManager authenticationManager;
private TokenExtractor tokenExtractor = new BearerTokenExtractor();
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new OAuth2AuthenticationDetailsSource();
#Override
public void process(Exchange exchange) throws Exception {
HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class);
Subject subject = new Subject();
Authentication auth = getAuth(request);
subject.getPrincipals().add(auth);
exchange.getIn().setHeader(Exchange.AUTHENTICATION, subject);
}
private Authentication getAuth(HttpServletRequest request) throws OAuth2Exception {
Authentication authentication = null;
try {
authentication = tokenExtractor.extract(request);
if (authentication != null) {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
return authenticationManager.authenticate(authentication);
}
} catch (Exception e) {
throw new OAuth2Exception(e.getMessage());
}
throw new OAuth2Exception("Not Authorized to view resource");
}
}
As a final solution I decided to use Spring Boot embedded servlet container instead of Apache Camel rest component. So it could be easily secured by Spring Security. This could be done by creating additional beans:
#Bean
public ServletRegistrationBean servletRegistrationBean() {
SpringServerServlet serverServlet = new SpringServerServlet();
ServletRegistrationBean regBean = new ServletRegistrationBean(serverServlet, "/camel/*");
Map<String, String> params = new HashMap<>();
params.put("org.restlet.component", "restletComponent");
regBean.setInitParameters(params);
return regBean;
}
#Bean
public Component restletComponent() {
return new Component();
}
#Bean
public RestletComponent restletComponentService() {
return new RestletComponent(restletComponent());
}

Resources