We have a requirement to verify whether a username exists in database and then authenticate against AD. If username doesn’t exist application will return error instead of trying to authenticate against AD. I have authenticated against multiple AD’s and/or database but I have trouble getting this to work. Any hints would be helpful. Thank you
In my class that extends WebSecurityConfigurerAdapter I tried to play with authenticationProvider where I could verify the existence in DB. But not sure of what to return so that the authentication could be proceed to LDAP.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(authenticationProvider)
.authenticationEventPublisher(authenticationEventPublisher)
.ldapAuthentication()
.....;
}
I also tried adding a before/after filter but not successful in there either
#Override
protected void configure(HttpSecurity http) throws Exception {
http
....
.and()
.addFilterBefore(preAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
;
In the filter preAuthenticationFilter the instance of request passed in doFilter() method FirewalledRequest. From this instance I am unable to get the username; looks like this is by design. If anyone has any advice on how we could retrieve username from the instance of FirewalledRequest please share it here. I will give it a try.
So instead of using the filter I decided to play with the custom AuthenticationProvider. In the AuthenticationProvider implementation under the method authenticate() I return null (and log, notify, etc.) when user exist. If user doesn’t exits I return the same instance of authentication passed. This breaks the chain and stops proceeding to authenticating against AD. Throwing any instance of AuthenticationException doesn’t work as Spring security captures this exception and proceeds further (per docs).
Here is how the code snippet looks like
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
Optional<User> user = service.findUserByUsername((String) authentication.getPrincipal());
if (user.isPresent())
return null;
return authentication;
}
Please share any better ideas.
#gandr solution got me thinking, and my final solution was:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(PreLdapAuthenticationProvider)
// authenticationEventPublisher not used anymore
.ldapAuthentication()
.....;
private static class PreLdapAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
this.userService.checkUserEnabled((String) authentication.getPrincipal());
return null;
}
}
public class UserService {
public void checkUserEnabled(String username) throws AuthenticationException {
UserEntity entity = userRepository.findByLogin(username);
if (entity == null) {
throw new PreLdapUsernameNotFoundException();
// normal UsernameNotFoundException extends AuthenticationException
// and will be caught and ignore, but not if my custom class
// extends AccountStatusException
}
if (!entity.isEnabled()) {
throw new DisabledException("DisabledException");
}
}
}
public class PreLdapUsernameNotFoundException extends AccountStatusException {
public PreLdapUsernameNotFoundException() {
super("PreLdapUsernameNotFoundException");
}
}
Then you can catch PreLdapUsernameNotFoundException and InternalAuthenticationServiceException in
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, authenticationException) -> {
if (authenticationException instanceof PreLdapUsernameNotFoundException){
...
} else ...
}
}
Related
I'm writing a simple REST API using Spring Boot and I want to enable basic authentication. Therefore I have used the WebSecurityConfigurerAdapter as shown below. For simplicity, I just want to check only the password (pwd123) and allow any user to log in. Please refer to the code below.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null || authentication.getCredentials() == null) {
throw new BadCredentialsException("Bad credentials");
}
if (authentication.getCredentials().equals("pwd123")) {
return new UsernamePasswordAuthenticationToken(authentication.getName(),
authentication.getCredentials().toString(),
Collections.emptyList());
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
});
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic();
}
}
Assume user_A has accessed the REST API with a valid password, i.e pwd123, and then do the send API call with a wrong password. However the user is allowed to access the API which is the problem.
When I do the debugging I realized that authenticationIsRequired function in BasicAuthenticationFilter class which is in Spring Security, returns false in such scenario. Please refer that code.
private boolean authenticationIsRequired(String username) {
// Only reauthenticate if username doesn't match SecurityContextHolder and user
// isn't authenticated (see SEC-53)
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if (existingAuth == null || !existingAuth.isAuthenticated()) {
return true;
}
// Limit username comparison to providers which use usernames (ie
// UsernamePasswordAuthenticationToken) (see SEC-348)
if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
return true;
}
// Handle unusual condition where an AnonymousAuthenticationToken is already
// present. This shouldn't happen very often, as BasicProcessingFitler is meant to
// be earlier in the filter chain than AnonymousAuthenticationFilter.
// Nevertheless, presence of both an AnonymousAuthenticationToken together with a
// BASIC authentication request header should indicate reauthentication using the
// BASIC protocol is desirable. This behaviour is also consistent with that
// provided by form and digest, both of which force re-authentication if the
// respective header is detected (and in doing so replace/ any existing
// AnonymousAuthenticationToken). See SEC-610.
return (existingAuth instanceof AnonymousAuthenticationToken);
}
Please let me know what is missing in my implementation
As mentioned in the comments, instead of providing a custom AuthenticationProvider you can try providing a custom UserDetailsService. Here's the complete configuration:
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests((authorizeRequests) -> authorizeRequests
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
#Bean
public UserDetailsService userDetailsService() {
return (username) -> new User(username, "{noop}pwd123", AuthorityUtils.createAuthorityList("ROLE_USER"));
}
}
When you evolve to looking up the user via a third-party service, you can add the code to do this in the custom UserDetailsService (a lambda function or an actual class that implements the interface) and continue returning a org.springframework.security.core.userdetails.User.
Note: I don't actually recommend plain-text passwords in production. You would replace {noop}pwd123 with something like {bcrypt}<bcrypt encoded password here>.
As suggested in the comments and answers, even if you use the InMemoryUserDetailsManager the problem does not get resolved, which means, once the user is authenticated with the correct user name and password, his password is not validated in the subsequent REST API calls,i.e. can use any password. This is because of the functionality in BasicAuthenticationFilter class where it skips users who are having a valid JSESSION cookie.
To fix the issue, we should configure http to create state-less sessions via
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
in configure function of the WebSecurityConfigurerAdapter
Please refer Why BasicAuthenticationFilter in spring security matches only username and not the password
I need to use a custom LdapAuthenticationProvider, with only one minor change, in order to execute the authentication, a certain precondition needs to be met.
What I want basically:
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!precondition) {
throw new DisabledException("");
}
return super.authenticate(authentication);
}
My WebSecurityConfigurerAdapter:
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(encoder);
if (ldapSecurityConfig.isLdapEnabled()) {
auth
.ldapAuthentication()
.contextSource(ldapContextSource)
.userSearchFilter(ldapSecurityConfig.getUserSearchFilter())
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator)
.userDetailsContextMapper(userDetailsContextMapper);
}
}
The problem is, that the line
auth.ldapAuthentication()
creates an LdapAuthenticationProviderConfigurer object, and its build method instantiates an LdapAuthenticationProvider object:
LdapAuthenticationProvider ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapAuthenticator, authoritiesPopulator);
It looks like I don't have control over which LdapAuthenticationProvider will be used at the end.
As a workaround, I could check the precondition in my UserDetailsContextMapper object and throw an exception if it is not met, but it is not optimal, since in this case the LDAP server will be queried even if it's not needed.
My question is, how can I force that my custom provider will be used, or is there any other "simple" way to achieve the behaviour I want?
Currently, whenever a user fails authentication, spring security responds with:
{"error": "invalid_grant","error_description": "Bad credentials"}
And I would like to enhance this response with a response code like:
{"responsecode": "XYZ","error": "invalid_grant","error_description": "Bad credentials"}
After some poking around, it looks like what I need to do this is implement an AuthenticationFailureHandler, which I have begun to do. However, the onAuthenticationFailure method never seems to be reached whenever I submit invalid login credentials. I have stepped through the code, and placed logging in the onAuthenticationFailure method to confirm it is not being reached.
My failure handler is:
#Component
public class SSOAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
response.addHeader("responsecode", "XYZ");
}
}
And my WebSecurityConfigurerAdapter contains:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired SSOAuthenticationFailureHandler authenticationFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.formLogin().failureHandler(authenticationFailureHandler);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(service).passwordEncoder(passwordEncoder());
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
}
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(){
return new DefaultAuthenticationEventPublisher();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public SSOAuthenticationFailureHandler authenticationHandlerBean() {
return new SSOAuthenticationFailureHandler();
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
My questions are:
Is this the right way to achieve the result I want? (Customizing the spring security authentication response)
If so, did I do something wrong trying to set up my authentication failure handler (since a bad login doesn't seem to be reaching the onAuthenticationFailure method?
Thank you!
You can add exception handling to you Spring Security by calling .exceptionHandling() on your HttpSecurity object in your configure method.
If you only want to handle just bad credentials you can ignore the .accessDeniedHandler(accessDeniedHandler()).
The access denied handler handles situations where you hav secured you app at method level such as using the #PreAuthorized, #PostAuthorized, & #Secured.
An example of your security config could be like this
SecurityConfig.java
/*
The following two are the classes we're going to create later on.
You can autowire them into your Security Configuration class.
*/
#Autowired
private CustomAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
/*
Adds exception handling to you HttpSecurity config object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.exceptionHandling()
.authencationEntryPoint(unauthorizedHandler) // handles bad credentials
.accessDeniedHandler(accessDeniedHandler); // You're using the autowired members above.
http.formLogin().failureHandler(authenticationFailureHandler);
}
/*
This will be used to create the json we'll send back to the client from
the CustomAuthenticationEntryPoint class.
*/
#Bean
public Jackson2JsonObjectMapper jackson2JsonObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
return new Jackson2JsonObjectMapper(mapper);
}
CustomAuthenticationEntryPoint.java
You can create this in its own separate file. This is Entry point handles the invalid credentials.
Inside the method we'll have to create and write our own JSON to the HttpServletResponse object. We'll
use the Jackson object mapper bean we created in the Security Config.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
#Autowired // the Jackson object mapper bean we created in the config
private Jackson2JsonObjectMapper jackson2JsonObjectMapper;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException {
/*
This is a pojo you can create to hold the repsonse code, error, and description.
You can create a POJO to hold whatever information you want to send back.
*/
CustomError error = new CustomError(HttpStatus.FORBIDDEN, error, description);
/*
Here we're going to creat a json strong from the CustomError object we just created.
We set the media type, encoding, and then get the write from the response object and write
our json string to the response.
*/
try {
String json = jackson2JsonObjectMapper.toJson(error);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write(json);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
CustomAccessDeniedHandler.java
This handles authorization errors such as trying to access method without the
appropriate priviledges. You can implement it in the same way we did above with the bad credentials exception.
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
// You can create your own repsonse here to handle method level access denied reponses..
// Follow similar method to the bad credentials handler above.
}
}
Hopefully this is somewhat helpful.
Currently, whenever a user fails authentication, spring security responds with:
{"error": "invalid_grant","error_description": "Bad credentials"}
And I would like to enhance this response with a response code like:
{"responsecode": "XYZ","error": "invalid_grant","error_description": "Bad credentials"}
After some poking around, it looks like what I need to do this is implement an AuthenticationFailureHandler, which I have begun to do. However, the onAuthenticationFailure method never seems to be reached whenever I submit invalid login credentials. I have stepped through the code, and placed logging in the onAuthenticationFailure method to confirm it is not being reached.
My failure handler is:
#Component
public class SSOAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
super.onAuthenticationFailure(request, response, exception);
response.addHeader("responsecode", "XYZ");
}
}
And my WebSecurityConfigurerAdapter contains:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired SSOAuthenticationFailureHandler authenticationFailureHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.formLogin().failureHandler(authenticationFailureHandler);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(service).passwordEncoder(passwordEncoder());
auth.authenticationEventPublisher(defaultAuthenticationEventPublisher());
}
#Bean
public DefaultAuthenticationEventPublisher defaultAuthenticationEventPublisher(){
return new DefaultAuthenticationEventPublisher();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public SSOAuthenticationFailureHandler authenticationHandlerBean() {
return new SSOAuthenticationFailureHandler();
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
My questions are:
Is this the right way to achieve the result I want? (Customizing the spring security authentication response)
If so, did I do something wrong trying to set up my authentication failure handler (since a bad login doesn't seem to be reaching the onAuthenticationFailure method?
Thank you!
You can add exception handling to you Spring Security by calling .exceptionHandling() on your HttpSecurity object in your configure method.
If you only want to handle just bad credentials you can ignore the .accessDeniedHandler(accessDeniedHandler()).
The access denied handler handles situations where you hav secured you app at method level such as using the #PreAuthorized, #PostAuthorized, & #Secured.
An example of your security config could be like this
SecurityConfig.java
/*
The following two are the classes we're going to create later on.
You can autowire them into your Security Configuration class.
*/
#Autowired
private CustomAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private CustomAccessDeniedHandler accessDeniedHandler;
/*
Adds exception handling to you HttpSecurity config object.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.exceptionHandling()
.authencationEntryPoint(unauthorizedHandler) // handles bad credentials
.accessDeniedHandler(accessDeniedHandler); // You're using the autowired members above.
http.formLogin().failureHandler(authenticationFailureHandler);
}
/*
This will be used to create the json we'll send back to the client from
the CustomAuthenticationEntryPoint class.
*/
#Bean
public Jackson2JsonObjectMapper jackson2JsonObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
return new Jackson2JsonObjectMapper(mapper);
}
CustomAuthenticationEntryPoint.java
You can create this in its own separate file. This is Entry point handles the invalid credentials.
Inside the method we'll have to create and write our own JSON to the HttpServletResponse object. We'll
use the Jackson object mapper bean we created in the Security Config.
#Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
#Autowired // the Jackson object mapper bean we created in the config
private Jackson2JsonObjectMapper jackson2JsonObjectMapper;
#Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException e) throws IOException {
/*
This is a pojo you can create to hold the repsonse code, error, and description.
You can create a POJO to hold whatever information you want to send back.
*/
CustomError error = new CustomError(HttpStatus.FORBIDDEN, error, description);
/*
Here we're going to creat a json strong from the CustomError object we just created.
We set the media type, encoding, and then get the write from the response object and write
our json string to the response.
*/
try {
String json = jackson2JsonObjectMapper.toJson(error);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write(json);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
CustomAccessDeniedHandler.java
This handles authorization errors such as trying to access method without the
appropriate priviledges. You can implement it in the same way we did above with the bad credentials exception.
#Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
// You can create your own repsonse here to handle method level access denied reponses..
// Follow similar method to the bad credentials handler above.
}
}
Hopefully this is somewhat helpful.
I have a CustomLoginSucessHandler in my Spring MVC 4 project to manage an action when the user Logs In.
This is working properly. In the same class I have the method determineTargetUrl to redirect the user according to his ROLE.
Here is the code:
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response){
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
final String userName = authentication.getName();
log.debug("TARGET URL METHOD!");
List<Authority> authorityList = authorityService.getAllAuthoritiesByUserName(userName);
for(Authority authority: authorityList){
switch (authority.getAuthority()){
case "ROLE_ADMIN":
return "processFile";
case "ROLE_USER":
return "userPortal";
case "ROLE_DEMO1":
return "processFile";
case "ROLE_DEMO2":
return "processFile";
}
}
return "403";
}
See that I have a log.debug("TARGET URL METHOD")
This log is never called and of course the page is not being redirected, it's going to the default landing page that is processFile.html.
I am puzzled why the second method is not being called while my onAuthenticationSuccess works perfectly. They are in the same Class.
Here is the code how I create the instance of my CustomLoginSucessHandler:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private CustomLoginSucessHandler customLoginSucessHandler;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login.html")
.loginProcessingUrl("/login").permitAll().and().logout().logoutSuccessUrl("/")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll().and().exceptionHandling()
.accessDeniedPage("/403.html");
http.csrf().requireCsrfProtectionMatcher(new CsrfRequestMatcher());
http.formLogin().successHandler(customLoginSucessHandler);
}
Thank you.
You are trying to ovverride the wrong function, that is the root cause of your issue. In the excerpt you provided you have a function that seems to be overriding another:
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response){
but in fact it is not overriding anything. If you check the javadoc of AuthenticationSuccessHandler, you will see that it provides only one function: onAuthenticationSuccess which you reported as "working". It works, but it is an overriden function and it does get called as part of the standard login procedure. If you follow closely this example:
CustomLoginSuccessHandler example (probably you followed this already)
you will see that the determineTargetUrl function is not overriden, but explicitly called by the implementation:
protected void handle(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
String targetUrl = determineTargetUrl(authentication);
which handle method in turn is also being called from:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}