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

 更新时间:2019-05-26 17:00:51   作者:佚名   我要评论(0)

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

概述

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

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

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

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

2. Maven设置

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

<parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.0.0.M7</version>
 <relativePath/>
</parent>
 
<dependencies>
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
 </dependency>
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
  <groupId>org.thymeleaf.extras</groupId>
  <artifactId>thymeleaf-extras-springsecurity4</artifactId>
 </dependency>
</dependencies>

可以在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提取的额外的字段:

<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>
 <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" rel="external nofollow" th:href="@{/index}" rel="external nofollow" >Back to home page</a></p>
</form>

当我们运行应用程序并访问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上找到。

总结

以上所述是小编给大家介绍的Spring Security在标准登录表单中添加一个额外的字段,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

您可能感兴趣的文章:

  • 详解spring security之httpSecurity使用示例
  • SpringBoot结合SpringSecurity实现图形验证码功能
  • SpringBoot + Spring Security 基本使用及个性化登录配置详解
  • 详解如何在spring boot中使用spring security防止CSRF攻击
  • spring security 5.x实现兼容多种密码的加密方式
  • 详解使用Spring Security OAuth 实现OAuth 2.0 授权
  • SpringBoot+SpringSecurity处理Ajax登录请求问题(推荐)
  • Spring Security结合JWT的方法教程

相关文章

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

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

    概述 在本文中,我们将通过向标准登录表单添加额外字段来实现Spring Security的自定义身份验证方案。 我们将重点关注两种不同的方法,以展示框架的多功能性以
    2019-05-26
  • java AOP原理以及实例用法总结

    java AOP原理以及实例用法总结

    AOP : 面向切面编程 在程序设计中,我们需要满足高耦合低内聚,所以编程需满足六大原则,一个法则. AOP面向切面编程正是为了满足这些原则的一种编程思想. 一.装饰
    2019-05-26
  • Node.js 多进程处理CPU密集任务的实现

    Node.js 多进程处理CPU密集任务的实现

    Node.js 单线程与多进程 大家都知道 Node.js 性能很高,是以异步事件驱动、非阻塞 I/O 而被广泛使用。但缺点也很明显,由于 Node.js 是单线程程序,如果长
    2019-05-26
  • C#使用Json.Net进行序列化和反序列化及定制化

    C#使用Json.Net进行序列化和反序列化及定制化

    序列化(Serialize)是将对象转换成字节流,并将其用于存储或传输的过程,主要用途是保存对象的状态,以便在需要时重新创建该对象;反序列化(Deserialize)则
    2019-05-26
  • C#SuperSocket的搭建并配置启动总结

    C#SuperSocket的搭建并配置启动总结

    之前我们借助一个SuperSocket实现了一个简易版的服务器, 但是不管是Server还是Session都是使用框架的,本篇博客我们要实现自己的Server和Session,来重写框架原
    2019-05-26
  • C#编程中常见数据结构的比较(Unity3D游戏开发)

    C#编程中常见数据结构的比较(Unity3D游戏开发)

    一.前言 Unity3D是如今最火爆的游戏开发引擎,它可以让我们能轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型的互动内容。它支持2D/3D游戏开发,
    2019-05-26
  • numpy linalg模块的具体使用方法

    numpy linalg模块的具体使用方法

    最近在看机器学习的 LogisticRegressor,BayesianLogisticRegressor算法,里面得到一阶导数矩阵g和二阶导数Hessian矩阵H的时候,用到了这个模块进行求解运算,
    2019-05-26
  • Docker私服仓库Harbor安装的步骤详解

    Docker私服仓库Harbor安装的步骤详解

    Harbor安装那里还是很简单,就是在Docker Login那里掉坑里去了,搞半天,写博客的时候,又重新安装了一遍 1.准备两台服务器 centos7 harbor 10.19.46
    2019-05-26
  • python numpy实现文件存取的示例代码

    python numpy实现文件存取的示例代码

    NumPy提供了多种存取数组内容的文件操作函数。保存数组数据的文件可以是二进制格式或者文本格式。二进制格式的文件又分为NumPy专用的格式化二进制类型和无格式
    2019-05-26
  • .NET Core 3.0之创建基于Consul的Configuration扩展组件

    .NET Core 3.0之创建基于Consul的Configuration扩展组件

    经过前面三篇关于.NET Core Configuration的文章之后,本篇文章主要讨论如何扩展一个Configuration组件出来。 了解了Configuration的源码后,再去扩展一个组件
    2019-05-26

最新评论