Spring Security custom ldapAuthenticationProvider + custom ldapAuthoritiesPopulator - spring-security

Is it possible to have a custom ldap authentication provider along with custom ldap authorities populator?
I don't want to restart my application each time ldap server is unreachable for a short moment (So i need the custom provider, to create a new context and override authenticate method on each login).
On the other side, i need to create custom roles for each membership of ldap user (need to override the getGrantedAuthorities)

For implementing custom ldap authentication provider you need to create class that extends from AbstractLdapAuthenticator
public class BindPasswordAuthentificator extends AbstractLdapAuthenticator {
public BindPasswordAuthentificator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
#Override
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user;
String username = authentication.getName();
String password = (String)authentication.getCredentials();
user = authenticateByLdap(username, password); // authenticate user here
if (user == null) {
throw new BadCredentialsException(
messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
}
And for implementing ldap authorities populator you need to create class that extends from LdapAuthoritiesPopulator
public class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Collection<GrantedAuthority> gauth = new HashSet<>();
//you need to place logic for populating user authorities here
return gauth;
}
}
After that you need to configure these two classes in your configuration
#Configuration
#PropertySource("classpath:application.properties")
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${life.ldap.server}")
private String ldapServer;
#Autowired
public void globalUserDetails(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(ldapAuthenticationProvider());
}
#Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() {
return new LdapAuthenticationProvider(authentificator(), authPopulator());
}
#Bean
public BindPasswordAuthentificator authentificator() {
return new BindPasswordAuthentificator(contextSource());
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource(ldapServer);
}
#Bean
public CustomLdapAuthoritiesPopulator authPopulator() {
CustomLdapAuthoritiesPopulator result = new CustomLdapAuthoritiesPopulator();
return result;
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers("/oauth/token/revokeById/**").permitAll()
.antMatchers("/tokens/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
.and().csrf().disable();
}
}

Related

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);
}

UserDetailsChecker implementation to override isAccountNotLocked method

I am using UserDetails implementation for authentication which has methods
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
If I set isAccountNonLocked() to return false I am unable to log in anymore.
How can I implement own UserDetailsChecker to override exceptions thrown on checks and where I put my custom UserDetailsChecker, because I am not sure how this preauthentication checks work.
If you have a custom UserDetailsChecker, you would wire that up when configuring the AuthenticationManager:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(dao());
}
AuthenticationProvider dao() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPreAuthenticationChecks(new MyUserDetailsChecker());
return provider;
}
#Bean
public UserDetailsService userDetailsService() {
// ... expose a UserDetailsService
}
}
Note that you might already be publishing a UserDetailsService in another way, but I've included it here as a reminder that the authentication provider needs one in order to work.

authenticationFilter is not getting invoked in spring security

I having requirement of passing extra parameter along with username and password in spring security authentication process. After reading several threads I added custom authentication filter in spring security chain
below are my files
Filter class
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String role = request.getParameter("role");
request.getSession().setAttribute("role", role);
return super.attemptAuthentication(request, response);
}
SecurityConfig class
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
RestAuthenticationSuccessHandler customizeAuthenticationSuccessHandler;
#Autowired
RestAuthenticationFailureHandler restAuthenticationFailureHandler;
#Autowired
UserDetailsService userDetailsService;
#Autowired
PasswordEncoder passwordEncoder;
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private AccessDeniedHandler restAccessDeniedHandler;
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests().antMatchers("/api/common/**").permitAll()
.antMatchers("/api/student/**").access("hasRole('ROLE_STUDENT')")
.antMatchers("/api/staff/**").access("hasRole('ROLE_STAFF')").antMatchers("/sysAdmin/**").access("hasRole('ROLE_ADMIN')").and().formLogin()
.loginProcessingUrl("/api/common/login")
.successHandler(customizeAuthenticationSuccessHandler)
.failureHandler(restAuthenticationFailureHandler)
.usernameParameter("userName")
.passwordParameter("password")
.and().exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).accessDeniedHandler(restAccessDeniedHandler)
.and().csrf().disable();
}
#Bean
public UsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authFilter = new AuthenticationFilter();
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
}
Problem:
When I tried to authenticate user attemptAuthentication method of my custom filter class is not getting invoked and call directly go to UserDetails service class
Added FilterProcessingUrl to authenticateFilter object resolved the issue.
Updated authenticationFilter() method :
#Bean
public AuthenticationFilter authenticationFilter() throws Exception {
AuthenticationFilter authFilter = new AuthenticationFilter();
authFilter.setUsernameParameter("userName");
authFilter.setPasswordParameter("password");
authFilter.setFilterProcessesUrl("/api/common/login");
authFilter.setAuthenticationSuccessHandler(customizeAuthenticationSuccessHandler);
authFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
authFilter.setAuthenticationManager(authenticationManagerBean());
return authFilter;
}

Does anyone have a UserDetails Service Example for Spring Cloud OAuth2 and Active Directory?

I have a Spring Cloud (Edgeware.SR3) OAuth2 Authorization server configured with Custom JWT tokens. I'm getting an IllegalStateException, UserDetailsService is required error when I hit the token_refresh endpoint.
Does anyone have an example of doing a UserDetails Service for Active Directory for this scenario? I presume the call for refreshing the token is actually checking against AD if the user is still valid such as not disabled since last successful login.
Not shown is I'm also doing integrations to AWS Cognito in the custom token enhancer which is also all working. Just the refresh token is what remains.
#Configuration
public class ServiceConfig extends GlobalAuthenticationConfigurerAdapter {
#Value("${ldap.domain}")
private String DOMAIN;
#Value("${ldap.url}")
private String URL;
#Override
public void init(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(DOMAIN, URL);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
--------
#Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
#Autowired
public AuthorizationServerConfiguration(AuthenticationManager authenticationManager){
super();
this.authenticationManager = authenticationManager;
}
#Value("${signing.key}")
private String signingKey;
#Bean
public JwtAccessTokenConverter accessTokenConverter(){
final JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey(signingKey);
return accessTokenConverter;
}
#Bean
public TokenStore tokenStore(){
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices(){
final DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
return tokenServices;
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("${client.id}")
.secret("${client.secret}")
.authorizedGrantTypes("password","refresh_token","authorization_code","client_credentials")
.refreshTokenValiditySeconds(3600 *24)
.scopes("xx","xx")
.autoApprove("xxxx")
.accessTokenValiditySeconds(3600);
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints){
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(
Arrays.asList(tokenEnhancer(),accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST)
.accessTokenConverter(accessTokenConverter());
}
#Override//oauth/check_token?token={access_token}
public void configure(final AuthorizationServerSecurityConfigurer security)throws Exception {
security.checkTokenAccess("permitAll()");
super.configure(security);
}
#Bean
public TokenEnhancer tokenEnhancer(){
return new CustomTokenEnhancer();
}
}

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