How to Add Domain-Based Verification to Spring Security Login in Spring Boot 3
This article announces a Spring Boot 3 case collection with 90 permanent examples, then walks through extending Spring Security login to include domain verification by defining a User entity, repository, custom filter, security configuration, Thymeleaf login page, controller, and test endpoint, complete with code snippets and screenshots.
Spring Boot 3 case collection with over 90 practical articles is now available, promising permanent updates and providing subscribers with MD learning notes and full source code.
Environment: SpringBoot 3.4.0
1. Introduction
Spring Security is a powerful, highly customizable authentication and access‑control framework for Java enterprise applications. It offers authentication, authorization, encryption, session management and integrates seamlessly with Spring MVC.
2. Practical Example
2.1 Define Entity and Repository
<code>@Entity
@Table(name = "s_user")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String domain;
private String password;
// getters, setters
}
</code> <code>public interface UserRepository extends JpaRepository<User, Long> {
User findByUsernameAndDomain(String username, String domain);
}
</code>2.2 Custom Filter
<code>public class ExtraAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
ExtraHttpRequest req = new ExtraHttpRequest(request);
filterChain.doFilter(req, response);
}
}
</code> <code>public class ExtraHttpRequest extends HttpServletRequestWrapper {
public ExtraHttpRequest(HttpServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
if (SecurityConfig.LOGIN_NAME_PARAMETER.equals(name)) {
String username = super.getParameter(SecurityConfig.LOGIN_NAME_PARAMETER);
String domain = super.getParameter(SecurityConfig.LOGIN_DOMAIN_PARAMETER);
return username + Character.LINE_SEPARATOR + domain;
}
return super.getParameter(name);
}
}
</code>2.3 Security Configuration
<code>@Configuration
public class SecurityConfig {
public static final String LOGIN_NAME_PARAMETER = "username";
public static final String LOGIN_DOMAIN_PARAMETER = "domain";
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Throwable {
http.csrf(csrf -> csrf.disable());
http.authorizeHttpRequests(registry -> {
registry.requestMatchers("*.html", "*.css", "*.js", "/login").permitAll();
registry.requestMatchers("/**").authenticated();
});
http.formLogin(form -> {
form.loginPage("/login").usernameParameter(LOGIN_NAME_PARAMETER);
});
http.addFilterBefore(extraAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
ExtraAuthenticationFilter extraAuthenticationFilter() {
return new ExtraAuthenticationFilter();
}
@Bean
PasswordEncoder noopPasswordEncoder() {
return new PasswordEncoder() {
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword != null && encodedPassword != null && rawPassword.equals(encodedPassword);
}
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
};
}
}
</code>2.4 Custom UserDetailsService
<code>@Component
public class PackUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public PackUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String[] info = StringUtils.split(username, String.valueOf(Character.LINE_SEPARATOR));
return this.userRepository.findByUsernameAndDomain(info[0], info[1]);
}
}
</code>2.5 Custom Login Page (Thymeleaf)
Add dependencies:
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
</code>Configure Thymeleaf:
<code>spring:
thymeleaf:
prefix: classpath:/static/
suffix: .html
cache: false
</code>login.html (saved under classpath:/static):
<code><html lang="en">
<head>
<meta charset="UTF-8">
<title>Secure Login</title>
<link href="https://cdn.bootcdn.net/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<form class="form-signin" th:action="@{/login}" method="post">
<h2 class="form-signin-heading">Secure Login</h2>
<p th:if="${param.error}" class="error">错误的用户名/域, 密码</p>
<label for="username" class="sr-only">帐号</label>
<input type="text" id="username" name="username" class="form-control" placeholder="用户名" required autofocus/>
<label for="domain" class="sr-only">域</label>
<input type="text" id="domain" name="domain" class="form-control" placeholder="登录域" required/>
<label for="password" class="sr-only">密码</label>
<input type="password" id="password" name="password" class="form-control" placeholder="密码" required/>
<button class="btn btn-sm btn-primary btn-block" type="submit">登录</button>
<a th:href="@{/index}">返回</a>
</form>
</body>
</html>
</code>2.6 Login Controller
<code>@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
}
</code>2.7 Test Endpoint
<code>@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/query")
public ResponseEntity<Object> query() {
return ResponseEntity.ok("api query success");
}
}
</code>Accessing /api/query redirects to the custom login page; after successful authentication the request returns to the original endpoint.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.