BAEL-1418 Spring Security with Extra Login Fields (#3476)

* BAEL-1418 - spring security with extra login fields

* change delimeter for username/domain concatenation

* remove unnecessary class

* move source to spring-5-security module

* finish moving example code to spring-5-security module

* fix formatting in pom

* adjust spacing

* BAEL-1418 Spring Security with Extra Login Fields

* added additional custom example
* refactored and added tests

* remove final keywords and serialVersionUID constants
This commit is contained in:
chrisoberle 2018-01-22 18:41:03 -05:00 committed by Grzegorz Piwowarek
parent db903618f3
commit 3459d3ad47
26 changed files with 597 additions and 102 deletions

View File

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

View File

@ -0,0 +1,28 @@
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 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;
}
}

View File

@ -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 PasswordEncoder passwordEncoder;
private 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;
}
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;
import java.util.ArrayList;
import java.util.Collection;

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;
import java.util.Collection;
@ -8,7 +8,7 @@ public class User extends org.springframework.security.core.userdetails.User {
private static final long serialVersionUID = 1L;
private final String domain;
private String domain;
public User(String username, String domain, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,

View File

@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;
public interface UserRepository {

View File

@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldscustom;
import java.util.Optional;

View File

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

View File

@ -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;
@ -36,8 +36,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.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;

View File

@ -1,4 +1,4 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafieldssimple;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -9,7 +9,7 @@ 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 class SimpleAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public static final String SPRING_SECURITY_FORM_DOMAIN_KEY = "domain";

View File

@ -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;
@ -7,11 +7,11 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
public class SimpleUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
public SimpleUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}

View File

@ -0,0 +1,26 @@
package com.baeldung.loginextrafieldssimple;
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Repository;
@Repository("userRepository")
public class SimpleUserRepository implements UserRepository {
@Override
public User findUser(String username, String domain) {
if (StringUtils.isAnyBlank(username, domain)) {
return null;
} else {
Collection<? extends GrantedAuthority> authorities = new ArrayList<>();
User user = new User(username, domain,
"$2a$10$U3GhSMpsMSOE8Kqsbn58/edxDBKlVuYMh7qk/7ErApYFjJzi2VG5K", true,
true, true, true, authorities);
return user;
}
}
}

View File

@ -0,0 +1,21 @@
package com.baeldung.loginextrafieldssimple;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
public class User extends org.springframework.security.core.userdetails.User {
private String domain;
public User(String username, String domain, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.domain = domain;
}
public String getDomain() {
return domain;
}
}

View File

@ -0,0 +1,7 @@
package com.baeldung.loginextrafieldssimple;
public interface UserRepository {
public User findUser(String username, String domain);
}

View File

@ -0,0 +1,51 @@
package com.baeldung.loginextrafieldssimple;
import java.util.Optional;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WebController {
@RequestMapping("/")
public String root() {
return "redirect:/index";
}
@RequestMapping("/index")
public String index(Model model) {
getDomain().ifPresent(d -> {
model.addAttribute("domain", d);
});
return "index";
}
@RequestMapping("/user/index")
public String userIndex(Model model) {
getDomain().ifPresent(d -> {
model.addAttribute("domain", d);
});
return "user/index";
}
@RequestMapping("/login")
public String login() {
return "login";
}
private Optional<String> getDomain() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
String domain = null;
if (auth != null && !auth.getClass().equals(AnonymousAuthenticationToken.class)) {
User user = (User) auth.getPrincipal();
domain = user.getDomain();
}
return Optional.ofNullable(domain);
}
}

View File

@ -1,18 +1,8 @@
body {
font-family: sans;
font-size: 1em;
}
p.error {
font-weight: bold;
color: red;
}
div.logout {
float: right;
margin-right: 2em;;
}
.formfield {
margin: 0.5em;
padding: 0.3em;
}

View File

@ -1,24 +1,32 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" lang="en">
<head>
<title>Spring Security - Login With Extra Fields</title>
<title>Spring Security with Extra Fields</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()">
Logged in user: <span sec:authentication="name"></span> |
domain: <span th:text="${domain}">Some Domain</span>
<div class="container">
<div class="logout float-right" th:fragment="logout" sec:authorize="isAuthenticated()">
<p>Logged in: <span sec:authentication="name"></span> | <span th:text="${domain}">Some Domain</span>
</p>
<div>
<form action="#" th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
<button class="btn btn-sm btn-primary btn-block" type="submit">Logout</button>
</form>
</div>
</div>
<h1>Hello Spring Security</h1>
<h2>Hello Spring Security</h2>
<p>This is an unsecured page, but you can access the secured pages after authenticating.</p>
<ul>
<li>Go to the <a href="/user/index" th:href="@{/user/index}">secured pages</a></li>
</ul>
</div>
</body>
</html>

View File

@ -1,23 +1,36 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login page</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<h1>Login page</h1>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>Login page</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<div class="container">
<form class="form-signin" th:action="@{/login}" method="post">
<h2 class="form-signin-heading">Please sign in</h2>
<p>Example: user / domain / password</p>
<p th:if="${param.error}" class="error">Invalid user, password, or domain</p>
<form th:action="@{/login}" method="post">
<label for="username">Username</label>:
<input class="formfield" type="text" id="username" name="username" autofocus="autofocus" /> <br />
<label for="domain">Domain</label>:
<input class="formfield" type="text" id="domain" name="domain" /> <br />
<label for="password">Password</label>:
<input class="formfield" type="password" id="password" name="password" /> <br />
<input type="submit" value="Log in" />
</form>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required autofocus/>
</p>
<p>
<label for="domain" class="sr-only">Domain</label>
<input type="text" id="domain" name="domain" class="form-control" placeholder="Domain" required autofocus/>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required autofocus/>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button><br/>
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
</body>
</form>
</div>
</body>
</html>

View File

@ -1,13 +1,20 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security - Login With Extra Fields</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<div th:replace="index::logout"></div>
<h1>This is a secured page!</h1>
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
</body>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<title>Secured Page</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link href="http://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
<link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
<div class="container">
<div th:replace="index::logout"></div>
<h2>This is a secured page!</h2>
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
</div>
</body>
</html>

View File

@ -0,0 +1,46 @@
package com.baeldung.loginextrafields;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
public abstract class AbstractExtraLoginFieldsTest {
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private WebApplicationContext wac;
protected MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.apply(springSecurity(springSecurityFilterChain))
.build();
}
@Test
public void givenRootPathAccess_thenRedirectToIndex() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("/index*"));
}
@Test
public void givenSecuredResource_whenAccessUnauthenticated_thenRequiresAuthentication() throws Exception {
this.mockMvc.perform(get("/user/index"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/login"));
}
}

View File

@ -1,8 +1,7 @@
package com.baeldung.securityextrafields;
package com.baeldung.loginextrafields;
import static org.junit.Assert.assertEquals;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
@ -11,57 +10,26 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.baeldung.loginextrafieldscustom.ExtraLoginFieldsApplication;
import com.baeldung.loginextrafieldscustom.User;
@RunWith(SpringRunner.class)
@SpringJUnitWebConfig
@SpringBootTest(classes = SpringExtraLoginFieldsApplication.class)
public class SecurityExtraFieldsTest {
@Autowired
private FilterChainProxy springSecurityFilterChain;
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.apply(springSecurity(springSecurityFilterChain)).build();
}
@Test
public void givenRootPathAccess_thenRedirectToIndex() throws Exception {
this.mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("/index*"));
}
@Test
public void givenSecuredResource_whenAccessUnauthenticated_thenRequiresAuthentication() throws Exception {
this.mockMvc.perform(get("/user/index"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/login"));
}
@SpringBootTest(classes = ExtraLoginFieldsApplication.class)
public class LoginFieldsFullTest extends AbstractExtraLoginFieldsTest {
@Test
public void givenAccessSecuredResource_whenAuthenticated_thenAuthHasExtraFields() throws Exception {
@ -100,4 +68,5 @@ public class SecurityExtraFieldsTest {
Collection<? extends GrantedAuthority> authorities = new ArrayList<>();
return new User("myusername", "mydomain", "password", true, true, true, true, authorities);
}
}

View File

@ -0,0 +1,72 @@
package com.baeldung.loginextrafields;
import static org.junit.Assert.assertEquals;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import com.baeldung.loginextrafieldssimple.ExtraLoginFieldsApplication;
import com.baeldung.loginextrafieldssimple.User;
@RunWith(SpringRunner.class)
@SpringJUnitWebConfig
@SpringBootTest(classes = ExtraLoginFieldsApplication.class)
public class LoginFieldsSimpleTest extends AbstractExtraLoginFieldsTest {
@Test
public void givenAccessSecuredResource_whenAuthenticated_thenAuthHasExtraFields() throws Exception {
MockHttpServletRequestBuilder securedResourceAccess = get("/user/index");
MvcResult unauthenticatedResult = mockMvc.perform(securedResourceAccess)
.andExpect(status().is3xxRedirection())
.andReturn();
MockHttpSession session = (MockHttpSession) unauthenticatedResult.getRequest()
.getSession();
String loginUrl = unauthenticatedResult.getResponse()
.getRedirectedUrl();
User user = getUser();
mockMvc.perform(post(loginUrl)
.param("username", user.getUsername())
.param("password", user.getPassword())
.param("domain", user.getDomain())
.session(session)
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrlPattern("**/user/index"))
.andReturn();
mockMvc.perform(securedResourceAccess.session(session))
.andExpect(status().isOk());
SecurityContext securityContext
= (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Authentication auth = securityContext.getAuthentication();
assertEquals(((User)auth.getPrincipal()).getDomain(), user.getDomain());
}
private User getUser() {
Collection<? extends GrantedAuthority> authorities = new ArrayList<>();
return new User("myusername", "mydomain", "password", true, true, true, true, authorities);
}
}