当前位置: 移动技术网 > IT编程>开发语言>Java > Spring Security在标准登录表单中添加一个额外的字段

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

2019年07月19日  | 移动技术网IT编程  | 我要评论

中茶5961,死神来了5快播,金达莱花中文版歌词

概述

在本文中,我们将通过向标准登录表单添加额外字段来实现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在标准登录表单中添加一个额外的字段,希望对大家有所帮助

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网