热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringSecurity在标准登录表单中添加一个额外的字段

概述在本文中,我们将通过向标准登录表单添加额外字段来实现SpringSecurity的自定义身份验证方案。我们将重点关注两种不同的方法,以展示框架的多功能性以及我们可以使用它的灵活方式。

概述

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

我们将重点关注两种不同的方法,以展示框架的多功能性以及我们可以使用它的灵活方式

我们的第一种方法是一个简单的解决方案,专注于重用现有的核心Spring Security实现

我们的第二种方法是更加定制的解决方案,可能更适合高级用例。

2. Maven设置

我们将使用Spring Boot启动程序来引导我们的项目并引入所有必需的依赖项。
我们将使用的设置需要父声明,Web启动器和安全启动器;我们还将包括thymeleaf :


    org.springframework.boot
    spring-boot-starter-parent
    2.0.0.M7
    

  

    
        org.springframework.boot
        spring-boot-starter-web
    
    
        org.springframework.boot
        spring-boot-starter-security
    
    
        org.springframework.boot
        spring-boot-starter-thymeleaf
     
     
        org.thymeleaf.extras
        thymeleaf-extras-springsecurity4
    

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

3.简单的项目设置

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

关键组件包括:
  • SimpleAuthenticationFilter - UsernamePasswordAuthenticationFilter的扩展
  • SimpleUserDetailsService - UserDetailsService的实现
  • User - Spring Security提供的User类的扩展,它声明了我们的额外域字段
  • SecurityConfig - 我们的Spring Security配置,它将SimpleAuthenticationFilter插入到过滤器链中,声明安全规则并连接依赖项
  • login.html - 收集用户名,密码和域的登录页面

3.1. 简单Authentication Filter

在我们的SimpleAuthenticationFilter中,域和用户名字段是从请求中提取的。我们连接这些值并使用它们来创建UsernamePasswordAuthenticationToken的实例。

然后将令牌传递给AuthenticationProvider进行身份验证:

public class SimpleAuthenticationFilter
  extends UsernamePasswordAuthenticationFilter {
 
    @Override
    public Authentication attemptAuthentication(
      HttpServletRequest request, 
      HttpServletResponse response) 
        throws AuthenticationException {
 
        // ...
 
        UsernamePasswordAuthenticationToken authRequest
          = getAuthRequest(request);
        setDetails(request, authRequest);
         
        return this.getAuthenticationManager()
          .authenticate(authRequest);
    }
 
    private UsernamePasswordAuthenticationToken getAuthRequest(
      HttpServletRequest request) {
  
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        String domain = obtainDomain(request);
 
        // ...
 
        String usernameDomain = String.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(
          username, String.valueOf(Character.LINE_SEPARATOR));
        if (usernameAndDomain == null || usernameAndDomain.length != 2) {
            throw new UsernameNotFoundException("Username and domain must be provided");
        }
        User user = userRepository.findUser(usernameAndDomain[0], usernameAndDomain[1]);
        if (user == null) {
            throw new UsernameNotFoundException(
              String.format("Username not found for domain, username=%s, domain=%s", 
                usernameAndDomain[0], usernameAndDomain[1]));
        }
        return user;
    }
}

3.3. Spring Security配置

我们的设置与标准的Spring Security配置不同,因为我们在默认情况下通过调用addFilterBefore将SimpleAuthenticationFilter插入到过滤器链中:

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

我们可以使用提供的DaoAuthenticationProvider,因为我们使用SimpleUserDetailsService配置它。回想一下,我们的SimpleUserDetailsService知道如何解析我们的用户名和域字段,并返回在验证时使用的相应用户。

public AuthenticationProvider authProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userDetailsService);
    provider.setPasswordEncoder(passwordEncoder());
    return provider;
}

由于我们使用的是SimpleAuthenticationFilter,因此我们配置自己的AuthenticationFailureHandler以确保正确处理失败的登录尝试:

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

3.4.登录页面

我们使用的登录页面收集我们的SimpleAuthenticationFilter提取的额外的字段:


 
 

Example: user / domain / password

Invalid user, password, or domain


Back to home page

当我们运行应用程序并访问http:// localhost:8081上下文时,我们会看到一个访问安全页面的链接。单击该链接将显示登录页面。正如所料,我们看到了额外的域名字段
image

3.5.总结

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

因此,我们能够使用最少量的配置和其他代码添加对额外登录字段的支持

4.自定义项目设置

我们的第二种方法与第一种方法非常相似,但可能更适合于非平凡用例。

我们的第二种方法的关键组成部分包括:

  • CustomAuthenticationFilter - UsernamePasswordAuthenticationFilter的扩展
  • CustomUserDetailsService - 声明loadUserbyUsernameAndDomain方法的自定义接口
  • CustomUserDetailsServiceImpl - CustomUserDetailsService的实现
  • CustomUserDetailsAuthenticationProvider - AbstractUserDetailsAuthenticationProvider的扩展
  • CustomAuthenticationToken - UsernamePasswordAuthenticationToken的扩展
  • User - Spring Security提供的User类的扩展,它声明了我们的额外域字段
  • SecurityConfig - 我们的Spring Security配置,它将CustomAuthenticationFilter插入到过滤器链中,声明安全规则并连接依赖项
  • login.html - 收集用户名,密码和域的登录页面

4.1.自定义验证过滤器

在我们的CustomAuthenticationFilter中,我们从请求中提取用户名,密码和域字段。这些值用于创建CustomAuthenticationToken的实例,该实例将传递给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(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);
 
        // ...
 
        return new CustomAuthenticationToken(username, password, domain);
    }

4.2.自定义UserDetails服务

我们的CustomUserDetailsService合约定义了一个名为loadUserByUsernameAndDomain的方法。

们创建的CustomUserDetailsServiceImpl类只是实现并委托我们的CustomUserRepository来获取用户

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

4.3.自定义UserDetailsAuthenticationProvider

我们的CustomUserDetailsAuthenticationProvider将AbstractUserDetailsAuthenticationProvider和委托扩展到我们的CustomUserDetailService以检索用户。这个类最重要的特性是retrieveUser方法的实现。

请注意,我们必须将身份验证令牌强制转换为CustomAuthenticationToken才能访问我们的自定义字段

@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);
    }
 
    // ...
 
    return loadedUser;
}

4.4.总结

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

5.结论

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

  • 在我们简单的方法中,我们最小化了我们需要编写的代码量。通过使用自定义解析逻辑调整用户名,我们能够重用DaoAuthenticationProvider和UsernamePasswordAuthentication
  • 在我们更加个性化的方法中,我们通过扩展AbstractUserDetailsAuthenticationProvider并使用CustomAuthenticationToken提供我们自己的CustomUserDetailsService来提供自定义字段支持。
与往常一样,所有源代码都可以在GitHub上找到。

推荐阅读
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • RouterOS 5.16软路由安装图解教程
    本文介绍了如何安装RouterOS 5.16软路由系统,包括系统要求、安装步骤和登录方式。同时提供了详细的图解教程,方便读者进行操作。 ... [详细]
  • 本文由编程笔记小编整理,主要介绍了使用Junit和黄瓜进行自动化测试中步骤缺失的问题。文章首先介绍了使用cucumber和Junit创建Runner类的代码,然后详细说明了黄瓜功能中的步骤和Steps类的实现。本文对于需要使用Junit和黄瓜进行自动化测试的开发者具有一定的参考价值。摘要长度:187字。 ... [详细]
  • 本文介绍了Oracle存储过程的基本语法和写法示例,同时还介绍了已命名的系统异常的产生原因。 ... [详细]
  • REVERT权限切换的操作步骤和注意事项
    本文介绍了在SQL Server中进行REVERT权限切换的操作步骤和注意事项。首先登录到SQL Server,其中包括一个具有很小权限的普通用户和一个系统管理员角色中的成员。然后通过添加Windows登录到SQL Server,并将其添加到AdventureWorks数据库中的用户列表中。最后通过REVERT命令切换权限。在操作过程中需要注意的是,确保登录名和数据库名的正确性,并遵循安全措施,以防止权限泄露和数据损坏。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文介绍了如何在Azure应用服务实例上获取.NetCore 3.0+的支持。作者分享了自己在将代码升级为使用.NET Core 3.0时遇到的问题,并提供了解决方法。文章还介绍了在部署过程中使用Kudu构建的方法,并指出了可能出现的错误。此外,还介绍了开发者应用服务计划和免费产品应用服务计划在不同地区的运行情况。最后,文章指出了当前的.NET SDK不支持目标为.NET Core 3.0的问题,并提供了解决方案。 ... [详细]
author-avatar
手机用户2502863161
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有