Skip to content

Commit

Permalink
BAEL-1418 Spring Security with Extra Login Fields
Browse files Browse the repository at this point in the history
* added additional custom example
* refactored and added tests
  • Loading branch information
chrisoberle committed Jan 21, 2018
1 parent 6dc3ab1 commit a20a4c9
Show file tree
Hide file tree
Showing 26 changed files with 599 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.baeldung.loginextrafieldscustom;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

public static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "domain";

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {

if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}

CustomAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}

private CustomAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
String domain = obtainDomain(request);

if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
if (domain == null) {
domain = "";
}

return new CustomAuthenticationToken(username, password, domain);
}

private String obtainDomain(HttpServletRequest request) {
return request.getParameter(SPRING_SECURITY_FORM_DOMAIN_KEY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.baeldung.loginextrafieldscustom;

import java.util.Collection;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {

private static final long serialVersionUID = 1L;

private final String domain;

public CustomAuthenticationToken(Object principal, Object credentials, String domain) {
super(principal, credentials);
this.domain = domain;
super.setAuthenticated(false);
}

public CustomAuthenticationToken(Object principal, Object credentials, String domain,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.domain = domain;
super.setAuthenticated(true); // must use super, as we override
}

public String getDomain() {
return this.domain;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.baeldung.loginextrafieldscustom;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

/**
* The plaintext password used to perform
* PasswordEncoder#matches(CharSequence, String)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

private final PasswordEncoder passwordEncoder;
private final CustomUserDetailsService userDetailsService;

/**
* The password used to perform
* {@link PasswordEncoder#matches(CharSequence, String)} on when the user is
* not found to avoid SEC-2056. This is necessary, because some
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private String userNotFoundEncodedPassword;

public CustomUserDetailsAuthenticationProvider(PasswordEncoder passwordEncoder, CustomUserDetailsService userDetailsService) {
this.passwordEncoder = passwordEncoder;
this.userDetailsService = userDetailsService;
}

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {

if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(
messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}

String presentedPassword = authentication.getCredentials()
.toString();

if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(
messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}

@Override
protected void doAfterPropertiesSet() throws Exception {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}

@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication;
UserDetails loadedUser;

try {
loadedUser = this.userDetailsService.loadUserByUsernameAndDomain(auth.getPrincipal()
.toString(), auth.getDomain());
} catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials()
.toString();
passwordEncoder.matches(presentedPassword, userNotFoundEncodedPassword);
}
throw notFound;
} catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}

if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, "
+ "which is an interface contract violation");
}
return loadedUser;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.baeldung.loginextrafieldscustom;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public interface CustomUserDetailsService {

UserDetails loadUserByUsernameAndDomain(String username, String domain) throws UsernameNotFoundException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.baeldung.loginextrafieldscustom;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service("userDetailsService")
public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {

private final UserRepository userRepository;

public CustomUserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public UserDetails loadUserByUsernameAndDomain(String username, String domain) throws UsernameNotFoundException {
if (StringUtils.isAnyBlank(username, domain)) {
throw new UsernameNotFoundException("Username and domain must be provided");
}
User user = userRepository.findUser(username, domain);
if (user == null) {
throw new UsernameNotFoundException(
String.format("Username not found for domain, username=%s, domain=%s",
username, domain));
}
return user;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;

import java.util.ArrayList;
import java.util.Collection;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringExtraLoginFieldsApplication {
public class ExtraLoginFieldsApplication {

public static void main(String[] args) {
SpringApplication.run(SpringExtraLoginFieldsApplication.class, args);
SpringApplication.run(ExtraLoginFieldsApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.baeldung.loginextrafieldscustom;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
@PropertySource("classpath:/application-extrafields.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private CustomUserDetailsService userDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception {

http
.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/css/**", "/index").permitAll()
.antMatchers("/user/**").authenticated()
.and()
.formLogin().loginPage("/login")
.and()
.logout()
.logoutUrl("/logout");
}

public CustomAuthenticationFilter authenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider());
}

public AuthenticationProvider authProvider() {
CustomUserDetailsAuthenticationProvider provider
= new CustomUserDetailsAuthenticationProvider(passwordEncoder(), userDetailsService);
return provider;
}

public SimpleUrlAuthenticationFailureHandler failureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/login?error=true");
}

public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;

import java.util.Collection;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;

public interface UserRepository {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;

import java.util.Optional;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.baeldung.loginextrafieldssimple;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExtraLoginFieldsApplication {

public static void main(String[] args) {
SpringApplication.run(ExtraLoginFieldsApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldssimple;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
Expand Down Expand Up @@ -36,8 +36,8 @@ protected void configure(HttpSecurity http) throws Exception {
.logoutUrl("/logout");
}

public CustomAuthenticationFilter authenticationFilter() throws Exception {
CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
public SimpleAuthenticationFilter authenticationFilter() throws Exception {
SimpleAuthenticationFilter filter = new SimpleAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldssimple;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -9,7 +9,7 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public class SimpleAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

public static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "domain";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldssimple;

import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.userdetails.UserDetails;
Expand All @@ -7,11 +7,11 @@
import org.springframework.stereotype.Service;

@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
public class SimpleUserDetailsService implements UserDetailsService {

private final UserRepository userRepository;

public CustomUserDetailsService(UserRepository userRepository) {
public SimpleUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

Expand Down
Loading

0 comments on commit a20a4c9

Please sign in to comment.