Spring Security在標(biāo)準(zhǔn)登錄表單中添加一個(gè)額外的字段
概述
在本文中,我們將通過向標(biāo)準(zhǔn)登錄表單添加額外字段來實(shí)現(xiàn)Spring Security的自定義身份驗(yàn)證方案。
我們將重點(diǎn)關(guān)注兩種不同的方法,以展示框架的多功能性以及我們可以使用它的靈活方式。
我們的第一種方法是一個(gè)簡(jiǎn)單的解決方案,專注于重用現(xiàn)有的核心Spring Security實(shí)現(xiàn)。
我們的第二種方法是更加定制的解決方案,可能更適合高級(jí)用例。
2. Maven設(shè)置
我們將使用Spring Boot啟動(dòng)程序來引導(dǎo)我們的項(xiàng)目并引入所有必需的依賴項(xiàng)。
我們將使用的設(shè)置需要父聲明,Web啟動(dòng)器和安全啟動(dòng)器;我們還將包括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安全啟動(dòng)器。
3.簡(jiǎn)單的項(xiàng)目設(shè)置
在我們的第一種方法中,我們將專注于重用Spring Security提供的實(shí)現(xiàn)。特別是,我們將重用DaoAuthenticationProvider和UsernamePasswordToken,因?yàn)樗鼈兪恰伴_箱即用”的。
關(guān)鍵組件包括:
•SimpleAuthenticationFilter - UsernamePasswordAuthenticationFilter的擴(kuò)展
•SimpleUserDetailsService - UserDetailsService的實(shí)現(xiàn)
•User - Spring Security提供的User類的擴(kuò)展,它聲明了我們的額外域字段
•SecurityConfig - 我們的Spring Security配置,它將SimpleAuthenticationFilter插入到過濾器鏈中,聲明安全規(guī)則并連接依賴項(xiàng)
•login.html - 收集用戶名,密碼和域的登錄頁(yè)面
3.1. 簡(jiǎn)單Authentication Filter
在我們的SimpleAuthenticationFilter中,域和用戶名字段是從請(qǐng)求中提取的。我們連接這些值并使用它們來創(chuàng)建UsernamePasswordAuthenticationToken的實(shí)例。
然后將令牌傳遞給AuthenticationProvider進(jìn)行身份驗(yàn)證:
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.簡(jiǎn)單的UserDetails服務(wù)
UserDetailsService定義了一個(gè)名為loadUserByUsername的方法。我們的實(shí)現(xiàn)提取用戶名和域名。然后將值傳遞給我們的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配置
我們的設(shè)置與標(biāo)準(zhǔn)的Spring Security配置不同,因?yàn)槲覀冊(cè)谀J(rèn)情況下通過調(diào)用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,因?yàn)槲覀兪褂肧impleUserDetailsService配置它?;叵胍幌?,我們的SimpleUserDetailsService知道如何解析我們的用戶名和域字段,并返回在驗(yàn)證時(shí)使用的相應(yīng)用戶。
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.登錄頁(yè)面
我們使用的登錄頁(yè)面收集我們的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>
當(dāng)我們運(yùn)行應(yīng)用程序并訪問http:// localhost:8081上下文時(shí),我們會(huì)看到一個(gè)訪問安全頁(yè)面的鏈接。單擊該鏈接將顯示登錄頁(yè)面。正如所料,我們看到了額外的域名字段
image
3.5.總結(jié)
在我們的第一個(gè)例子中,我們能夠通過“偽造”用戶名字段來重用DaoAuthenticationProvider和UsernamePasswordAuthenticationToken。
因此,我們能夠使用最少量的配置和其他代碼添加對(duì)額外登錄字段的支持。
4.自定義項(xiàng)目設(shè)置
我們的第二種方法與第一種方法非常相似,但可能更適合于非平凡用例。
我們的第二種方法的關(guān)鍵組成部分包括:
•CustomAuthenticationFilter - UsernamePasswordAuthenticationFilter的擴(kuò)展
•CustomUserDetailsService - 聲明loadUserbyUsernameAndDomain方法的自定義接口
•CustomUserDetailsServiceImpl - CustomUserDetailsService的實(shí)現(xiàn)
•CustomUserDetailsAuthenticationProvider - AbstractUserDetailsAuthenticationProvider的擴(kuò)展
•CustomAuthenticationToken - UsernamePasswordAuthenticationToken的擴(kuò)展
•User - Spring Security提供的User類的擴(kuò)展,它聲明了我們的額外域字段
•SecurityConfig - 我們的Spring Security配置,它將CustomAuthenticationFilter插入到過濾器鏈中,聲明安全規(guī)則并連接依賴項(xiàng)
•login.html - 收集用戶名,密碼和域的登錄頁(yè)面
4.1.自定義驗(yàn)證過濾器
在我們的CustomAuthenticationFilter中,我們從請(qǐng)求中提取用戶名,密碼和域字段。這些值用于創(chuàng)建CustomAuthenticationToken的實(shí)例,該實(shí)例將傳遞給AuthenticationProvider進(jìn)行身份驗(yàn)證:
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服務(wù)
我們的CustomUserDetailsService合約定義了一個(gè)名為loadUserByUsernameAndDomain的方法。
我們創(chuàng)建的CustomUserDetailsServiceImpl類只是實(shí)現(xiàn)并委托我們的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和委托擴(kuò)展到我們的CustomUserDetailService以檢索用戶。這個(gè)類最重要的特性是retrieveUser方法的實(shí)現(xiàn)。
請(qǐng)注意,我們必須將身份驗(yàn)證令牌強(qiáng)制轉(zhuǎn)換為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.總結(jié)
我們的第二種方法幾乎與我們首先提出的簡(jiǎn)單方法相同。通過實(shí)現(xiàn)我們自己的AuthenticationProvider和CustomAuthenticationToken,我們避免了需要使用自定義解析邏輯來調(diào)整我們的用戶名字段。
5.結(jié)論
在本文中,我們?cè)赟pring Security中實(shí)現(xiàn)了一個(gè)使用額外登錄字段的表單登錄。我們以兩種不同的方式做到了這一點(diǎn)
•在我們簡(jiǎn)單的方法中,我們最小化了我們需要編寫的代碼量。通過使用自定義解析邏輯調(diào)整用戶名,我們能夠重用DaoAuthenticationProvider和UsernamePasswordAuthentication
•在我們更加個(gè)性化的方法中,我們通過擴(kuò)展AbstractUserDetailsAuthenticationProvider并使用CustomAuthenticationToken提供我們自己的CustomUserDetailsService來提供自定義字段支持。
與往常一樣,所有源代碼都可以在GitHub上找到。
總結(jié)
以上所述是小編給大家介紹的Spring Security在標(biāo)準(zhǔn)登錄表單中添加一個(gè)額外的字段,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- SpringSecurity表單配置之登錄成功及頁(yè)面跳轉(zhuǎn)原理解析
- Spring?Security登錄表單配置示例詳解
- SpringSecurity?表單登錄的實(shí)現(xiàn)
- SpringBoot基于SpringSecurity表單登錄和權(quán)限驗(yàn)證的示例
- SpringSecurity 自定義表單登錄的實(shí)現(xiàn)
- SpringSecurity 默認(rèn)表單登錄頁(yè)展示流程源碼
- Spring Security 表單登錄功能的實(shí)現(xiàn)方法
- 最新Spring?Security實(shí)戰(zhàn)教程之表單登錄定制到處理邏輯的深度改造(最新推薦)
相關(guān)文章
實(shí)例解析Java程序中正則表達(dá)式的貪婪模式匹配
貪婪模式又叫最大匹配,X?、X*、X+、X{n,}都是最大匹配,例如你要用“<.+>”去匹配“a<tr>aava </tr>abb”,也許你所期待的結(jié)果是想匹配“<tr>”,但是實(shí)際結(jié)果卻會(huì)匹配到“<tr>aava </tr>”,下面我們就來看具體看一下貪婪模式的使用.2016-05-05Java httpClient連接池支持多線程高并發(fā)的實(shí)現(xiàn)
本文主要介紹了Java httpClient連接池支持多線程高并發(fā)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Java定時(shí)任務(wù)ScheduledThreadPoolExecutor示例詳解
這篇文章主要介紹了Java定時(shí)任務(wù)ScheduledThreadPoolExecutor示例詳解,這里使用scheduleAtFixedRate方法安排一個(gè)任務(wù),該任務(wù)是一個(gè) Runnable 匿名類,其run方法中調(diào)用了new LoginViewTimeTask().loginStatisticsHandle()方法,需要的朋友可以參考下2023-11-11JavaWeb實(shí)現(xiàn)簡(jiǎn)單查詢商品功能
這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)簡(jiǎn)單查詢商品功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07SpringBoot操作spark處理hdfs文件的操作方法
本文介紹了如何使用Spring Boot操作Spark處理HDFS文件,包括導(dǎo)入依賴、配置Spark信息、編寫Controller和Service處理地鐵數(shù)據(jù)、運(yùn)行項(xiàng)目以及觀察Spark和HDFS的狀態(tài),感興趣的朋友跟隨小編一起看看吧2025-01-01詳解ConcurrentHashMap如何保證線程安全及底層實(shí)現(xiàn)原理
這篇文章主要為大家介紹了ConcurrentHashMap如何保證線程安全及底層實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05