带有Spring安全性的额外登录字段
最后修改:2021年1月21日
1.介绍
在本文中,我们将实现自定义身份验证方案Spring Security经过将额外的字段添加到标准登录表单中。
我们要专注于2种不同的方法,以显示该框架的多功能性和我们可以在其中灵活使用它的方式。
我们的第一个方法将是一个简单的解决方案,专注于重用现有的核心弹簧安全实现。
我们的第二个方法将是一个更自定义的解决方案,可能更适合高级用例。
我们将在我们的以前关于Spring Security登录的文章。
2. maven设置
我们将使用Spring Boot启动器来引导我们的项目,并引入所有必要的依赖项。
我们将使用的设置需要父声明,网络启动和安全启动器;我们还将包括胸部叶:
org.springframework.boot groupID> Spring-Boot-Starter-Parent Artifactid> 2.4.0 version> <相对路径/> 父> <依赖项><依赖> org.springframework.boot groupID> Spring-Boot-Starter-Web Artifactid> 依赖项> <依赖项> org.springFrameWork.Boot GroupID> 春天启动 - 启动器 - 安全性 ARTIFACTID> 依赖项> <依赖项> org.springframework.boot groupID> Spring-Boot-Starter-Tryheaf ArtifactId> 依赖项> <依赖项> org.thymeleaf.extras groupID> 胸部叶额 - SPRINGSECURY5 ARTIFACTID> 依赖> 依赖性>
可以找到Spring Boot安全启动器的最新版本在Maven中心。
3.简单的项目设置
在我们的第一种方法中,我们将着重于重用Spring Security提供的实现。特别是,我们将重用DaoAuthenticationProvider和UsernamePasswordToken因为它们存在“开箱即用”。
关键组件将包括:
- SimpleAuthenticationFilter-延伸UsernamepasswordauthenticationFilter.
- SimpleUserDetailsService.-实施UserDetailsService
- 我们呃-扩展用户Spring Security提供的类,它声明了我们额外的领域场
- SecurityConfig-我们的Spring Security配置,它插入我们的SimpleAuthenticationFilter在过滤器链中声明安全规则并连接依赖项
- login.html.-收集的登录页面用户名那密码,领域
3.1。简单身份验证过滤器
在我们的SimpleAuthenticationFilter那域和用户名字段从请求中提取。我们连接这些值并使用它们来创建一个实例UsernamePasswordAuthenticationToken。
然后将令牌传递给AuthenticationProvider进行身份验证:
public class SimpleAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException{//…UsernamePasswordAuthenticationToken authRequest = getAuthRequest(请求);setDetails(请求,authRequest);返回this.getAuthenticationManager () .authenticate (authRequest);} private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest请求){String用户名=获取用户名(请求);String password = obtainPassword(请求);String domain =获取域(请求);/ /……字符串usernameddomain =字符串。format("%s%s%s", username.trim(), String.valueOf(Character.LINE_SEPARATOR), domain); return new UsernamePasswordAuthenticationToken( usernameDomain, password); } // other methods }
3.2。简单的UserDetails服务
这UserDetailsService契约定义了一个被调用的方法loadUserByUsername。我们的实施提取了用户名和领域。然后将值传递给我们的UserRepository得到这一点用户:
public class SimpleUserDetailsService implements UserDetailsService{//…@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {String[] usernameAndDomain = StringUtils。split(用户名、String.valueOf (Character.LINE_SEPARATOR));if (usernameAndDomain == null || usernameAndDomain. name == null);= 2) {throw new UsernameNotFoundException("必须提供用户名和域");} User User = userRepository。findUser (usernameAndDomain [0], usernameAndDomain [1]);if (user == null) {throw new UsernameNotFoundException(String. UsernameNotFoundException);格式(“没有找到域的用户名,用户名=%s,域=%s”,用户名和域[0],用户名和域[1]));}返回用户;}}
3.3。春季安全配置
我们的设置不同于标准的Spring Security配置,因为我们插入SimpleAuthenticationFilter进入过滤器链之前的默认打电话addFilterBefore:
@override保护void配置(Httpsecurity HTTP)抛出异常{HTTP .addFilterBefore(AuthenticationFilter(),UsernamePasswordauthenticationFilter.class).AuthorizeRequests().AuthorizeRequests().AntMatchers(“/ CSS / **”,“/ index”)。Perpilall().AntMatchers(“/ user / **”)。认证().and().bormlogin()。loginpage(“/ login”).and().logout().logouturl(“/ logout”);}
我们能够使用提供的DaoAuthenticationProvider因为我们与我们配置了它SimpleUserDetailsService.。回顾我们的SimpleUserDetailsService.知道如何解析我们的用户名和领域领域并返回相应的用户验证时使用:
public AuthenticationProvider authProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService (userDetailsService);provider.setPasswordEncoder (passwordEncoder ());返回供应商;}
因为我们用了aSimpleAuthenticationFilter,我们自己配置AuthenticationFailureHandler为了确保正确地处理失败的登录尝试:
public simpleauthenticationfilter身份验证yilter()抛出异常{simpleauthenticationfilter filter = new simpleauthenticationfilter();filter.setauthenticationManager(AuthenticationManagerBean());filter.setauthenticationFailureHandler(fallueHandler());返回过滤器;}
3.4。登录页面
我们使用的登录页面收集我们的额外费用领域由我们提取的字段SimpleAuthenticationFilter:
< input type =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value =" submit " value ="提交"error}" class="error">无效用户,密码,或者域名
< label for="password" class="sr-only"> password
> < /形式
当我们运行应用程序并访问上下文时http:// localhost:8081,我们看到访问安全页面的链接。单击链接将导致登录页面显示。正如预期的那样,我们看到附加域字段:

3.5。概括
在我们的第一个例子中,我们能够重用DaoAuthenticationProvider和UsernamePasswordAuthenticationToken通过“伪造”用户名字段。
因此,我们能够为具有最小配置和其他代码的额外登录字段添加支持。
4.自定义项目设置
我们的第二种方法将与第一种方法非常相似,但可能更适合非平凡的用例。
我们的第二种方法的关键组成部分将包括:
- CustomAuthenticationFilter-延伸UsernamepasswordauthenticationFilter.
- customuserdetailsservice.-声明LoadUserbyusernameanDomain.方法
- CustomUserDetailsServiceImpl-的实现customuserdetailsservice.
- CustomUserDetailsAuthenticationProvider-延伸AbstractUserDetailsAuthenticationProvider
- CustomAuthenticationToken-延伸UsernamePasswordAuthenticationToken
- 我们呃-扩展用户Spring Security提供的类,它声明了我们额外的领域场
- SecurityConfig-我们的Spring Security配置,它插入我们的CustomAuthenticationFilter在过滤器链中声明安全规则并连接依赖项
- login.html.-收集的登录页面用户名那密码,领域
4.1。自定义身份验证过滤器
在我们的CustomAuthenticationFilter,我们从请求中提取用户名、密码和域字段。这些值用于创建我们的Custom的实例身份验证它被传递给了AuthenticationProvider身份验证:
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{//…CustomAuthenticationToken authRequest = getAuthRequest(请求);setDetails(请求,authRequest);返回this.getAuthenticationManager () .authenticate (authRequest);} private CustomAuthenticationToken getAuthRequest(HttpServletRequest请求){String用户名=获取用户名(请求);String password = obtainPassword(请求);String domain =获取域(请求);/ /……返回新的CustomAuthenticationToken(用户名,密码,域); }
4.2。风俗UserDetails服务
我们的customuserdetailsservice.契约定义了一个被调用的方法LoadUserByusernameanDDomain。
这CustomUserDetailsServiceImpl类只实现契约并委托给CustomUserRepository得到这一点用户:
public userdetails loadUserbyusernameanddomain(String用户名,字符串域)抛出UniNamenotFoundException {if(stringutils.isanyblank(用户名,域)){oppl新的usernamenotfoundexception(必须提供“用户名和域”);}用户= UserRepository.finduser(用户名,域);if(user == null){投掷新的usernamenotfoundException(string.format(“找不到域的用户名,UserName =%s,domain =%s”,用户名,域));}返回用户;}
4.3。风俗UserDetailsAuthenticationProvider
我们的CustomUserDetailsAuthenticationProvider延伸AbstractUserDetailsAuthenticationProvider和代表到我们的CustomUserDetailService检索用户。类的最重要的特性是检索器方法。
请注意,我们必须将身份验证令牌转换为CustomAuthenticationToken访问我们的自定义字段:
@Override protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken认证)抛出AuthenticationException {CustomAuthenticationToken auth = (CustomAuthenticationToken)认证;UserDetails loadedUser;try {loadduser = this。userDetailsService .loadUserByUsernameAndDomain(auth.getPrincipal() .toString(), auth.getDomain());} catch (UsernameNotFoundException notFound) {if (authentication.getCredentials() != null) {String presentedPassword = authentication.getCredentials() .toString();passwordEncoder。匹配(presentedPassword userNotFoundEncodedPassword);}扔notFound;}捕获(异常repositoryProblem){抛出新的InternalAuthenticationServiceException(repositoryProblem. getmessage (), repositoryProblem);} / /…… return loadedUser; }
4.4。概括
我们的第二种方法与我们首先提出的简单方法几乎相同。通过实现我们自己的AuthenticationProvider和CustomAuthenticationToken,我们避免使用自定义解析逻辑调整我们的用户名字段。
5.结论
在本文中,我们在Spring Security中实现了使用额外的登录字段的表单登录。我们以2种不同的方式做到这一点:
- 在简单的方法中,我们最小化了我们需要写的代码量。我们能够重复使用DaoAuthenticationProvider通过调整用户名来usernamepasswordauthication使用自定义解析逻辑
- 在我们更自定义的方法中,我们通过扩展AbstractUserDetailsAuthenticationProvider并提供我们自己的customuserdetailsservice.与A.CustomAuthenticationToken
一如既往,可以找到所有源代码在GitHub。