安全上

我刚刚宣布了新消息学习Spring Security课程,包括完整的材料集中在新的OAuth2堆栈在Spring Security 5:

>>看看这个课程

1.介绍

在本文中,我们将实现自定义身份验证方案Spring Security经过将额外的字段添加到标准登录表单中

我们要专注于2种不同的方法,以显示该框架的多功能性和我们可以在其中灵活使用它的方式。

我们的第一个方法将是一个简单的解决方案,专注于重用现有的核心弹簧安全实现。

我们的第二个方法将是一个更自定义的解决方案,可能更适合高级用例。

我们将在我们的以前关于Spring Security登录的文章

2. maven设置

我们将使用Spring Boot启动器来引导我们的项目,并引入所有必要的依赖项。

我们将使用的设置需要父声明,网络启动和安全启动器;我们还将包括胸部叶:

  org.springframework.boot   Spring-Boot-Starter-Parent   2.4.0  <相对路径/>  <依赖项><依赖>  org.springframework.boot   Spring-Boot-Starter-Web   <依赖项>  org.springFrameWork.Boot  春天启动 - 启动器 - 安全性  <依赖项>  org.springframework.boot   Spring-Boot-Starter-Tryheaf   <依赖项>  org.thymeleaf.extras  胸部叶额 -  SPRINGSECURY5   

可以找到Spring Boot安全启动器的最新版本在Maven中心

3.简单的项目设置

在我们的第一种方法中,我们将着重于重用Spring Security提供的实现。特别是,我们将重用DaoAuthenticationProviderUsernamePasswordToken因为它们存在“开箱即用”。

关键组件将包括:

  • SimpleAuthenticationFilter-延伸UsernamepasswordauthenticationFilter.
  • SimpleUserDetailsS​​ervice.-实施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因为我们与我们配置了它SimpleUserDetailsS​​ervice.。回顾我们的SimpleUserDetailsS​​ervice.知道如何解析我们的用户名领域领域并返回相应的用户验证时使用:

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。概括

在我们的第一个例子中,我们能够重用DaoAuthenticationProviderUsernamePasswordAuthenticationToken通过“伪造”用户名字段。

因此,我们能够为具有最小配置和其他代码的额外登录字段添加支持

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。概括

我们的第二种方法与我们首先提出的简单方法几乎相同。通过实现我们自己的AuthenticationProviderCustomAuthenticationToken,我们避免使用自定义解析逻辑调整我们的用户名字段。

5.结论

在本文中,我们在Spring Security中实现了使用额外的登录字段的表单登录。我们以2种不同的方式做到这一点:

  • 在简单的方法中,我们最小化了我们需要写的代码量。我们能够重复使用DaoAuthenticationProvider通过调整用户名来usernamepasswordauthication使用自定义解析逻辑
  • 在我们更自定义的方法中,我们通过扩展AbstractUserDetailsAuthenticationProvider并提供我们自己的customuserdetailsservice.与A.CustomAuthenticationToken

一如既往,可以找到所有源代码在GitHub

安全底部

我刚刚宣布了新消息学习Spring Security课程,包括完整的材料集中在新的OAuth2堆栈在Spring Security 5:

>>看看这个课程
评论在本文上关闭!