SpringSecurity實(shí)現(xiàn)自定義用戶認(rèn)證方案
Spring Security 實(shí)現(xiàn)自定義用戶認(rèn)證方案可以根據(jù)具體需求和業(yè)務(wù)場(chǎng)景進(jìn)行設(shè)計(jì)和實(shí)施,滿足不同的安全需求和業(yè)務(wù)需求。這種靈活性使得認(rèn)證機(jī)制能夠更好地適應(yīng)各種復(fù)雜的環(huán)境和變化。通過(guò)自定義認(rèn)證方案,可以更好地控制和管理用戶的訪問(wèn)權(quán)限,確保數(shù)據(jù)和應(yīng)用程序的安全性和可靠性。
基于 Spring Security 自定義用戶認(rèn)證方案的開(kāi)發(fā)流程如下圖:
UserDetails 接口代表用戶詳細(xì)信息,而負(fù)責(zé)對(duì) UserDetails 進(jìn)行各種操作的則是 UserDetailsService 接口。因此,實(shí)現(xiàn)自定義用戶認(rèn)證方案首先要做的是實(shí)現(xiàn) UserDetails 和 UserDetailsService 接口。同時(shí),如果擴(kuò)展了用信息,可以結(jié)合 AuthenticationProvider 接口來(lái)擴(kuò)展整個(gè)認(rèn)證流程。
1、SpringBoot 整合 SpringSecurity 框架
【示例】SpringBoot 整合 SpringSecurity 創(chuàng)建一個(gè)自定義用戶認(rèn)證應(yīng)用。
1.1 創(chuàng)建 Spring Boot 項(xiàng)目
創(chuàng)建 SpringBoot 項(xiàng)目,項(xiàng)目結(jié)構(gòu)如下圖:
1.2 添加 Maven 依賴
在 pom.xml 配置文件中添加 Spring Security、Thymeleaf 模板引擎、Lombok 依賴。
<!-- Spring Security 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.7.18</version> </dependency> <!-- Lombok 依賴 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 引入Thymeleaf模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2、Spring Security 實(shí)現(xiàn)自定義用戶認(rèn)證方案
Spring Security 所做的工作只是把常見(jiàn)的、符合一般業(yè)務(wù)場(chǎng)景的實(shí)現(xiàn)方法進(jìn)行抽象并嵌入框架中,開(kāi)發(fā)人員完全可以自定義用戶認(rèn)證方案。
2.1 擴(kuò)展 UserDetails
擴(kuò)展 UserDetails 的方法是直接實(shí)現(xiàn)該接口。
package com.pjb.model; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; /** * 自定義用戶認(rèn)證類:擴(kuò)展 UserDetails 接口 * @author pan_junbiao **/ @Data public class LoginUserDetails implements UserDetails { private Long userId; private String username; private String password; private String BlogName; //博客名稱 private String BlogUrl; //博客地址 private List<GrantedAuthority> authoritys; //權(quán)限集合 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return this.authoritys; } public void setAuthoritys(List<GrantedAuthority> authoritys) { this.authoritys = authoritys; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
2.2 擴(kuò)展 UserDetailsService
接下來(lái)我們實(shí)現(xiàn) UserDetailsService 接口。
package com.pjb.service; import com.pjb.model.LoginUserDetails; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.List; /** * 登錄服務(wù)類:擴(kuò)展 UserDetailsService 接口 * @author pan_junbiao **/ @Service public class LoginUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //模擬數(shù)據(jù)庫(kù)查詢 LoginUserDetails user = this.findByUsername(username); if (user != null) { return user; } //未查詢到用戶信息,則拋出異常 throw new UsernameNotFoundException("未找到用戶名稱為:" + username + "的用戶信息"); } /** * 模擬數(shù)據(jù)庫(kù)查詢 */ private LoginUserDetails findByUsername(String username) { LoginUserDetails loginUserDetails = null; if (username != null && username.equals("panjunbiao")) { loginUserDetails = new LoginUserDetails(); loginUserDetails.setUserId(1L); loginUserDetails.setUsername(username); loginUserDetails.setPassword("123456"); loginUserDetails.setBlogName("您好,歡迎訪問(wèn) pan_junbiao的博客"); loginUserDetails.setBlogUrl("https://blog.csdn.net/pan_junbiao"); //設(shè)置權(quán)限 List<GrantedAuthority> grantedAuthorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"); loginUserDetails.setAuthoritys(grantedAuthorityList); } return loginUserDetails; } }
2.3 擴(kuò)展 AuthenticationProvider
擴(kuò)展 AuthenticationProvider 是實(shí)現(xiàn)自定義認(rèn)證流程的最后一步,這個(gè)過(guò)程提供一個(gè)自定義的 AuthenticationProvider 實(shí)現(xiàn)類。
package com.pjb.provider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * 登錄認(rèn)證類:擴(kuò)展 AuthenticationProvider * @author pan_junbiao **/ @Component public class LoginAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { //從 Authentication 對(duì)象中獲取用戶名稱和身份憑證信息 String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userDetailsService.loadUserByUsername(username); if (passwordEncoder.matches(password, user.getPassword())) { //密碼匹配成功,則構(gòu)建一個(gè) UsernamePasswordAuthenticationToken 對(duì)象并返回 return new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()); } else { //密碼匹配失敗,則拋出異常 throw new BadCredentialsException("用戶密碼不正確"); } } @Override public boolean supports(Class<?> authenticationType) { return authenticationType.equals(UsernamePasswordAuthenticationToken.class); } }
3、Spring Security 的處理類
3.1 登錄成功處理類
package com.pjb.handler; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 登錄成功處理類 */ @Component public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { //重定向至首頁(yè) httpServletResponse.sendRedirect("/"); } }
3.2 登錄失敗處理類
package com.pjb.handler; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 登錄失敗處理類 */ @Component public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException authenticationException) throws IOException, ServletException { //獲取登錄失敗原因 String errorMessage = ""; if(authenticationException instanceof BadCredentialsException){ errorMessage = "用戶名或密碼不正確"; }else if(authenticationException instanceof DisabledException){ errorMessage = "賬號(hào)被禁用"; }else if(authenticationException instanceof UsernameNotFoundException){ errorMessage = "用戶名不存在"; }else if(authenticationException instanceof CredentialsExpiredException){ errorMessage = "密碼已過(guò)期"; }else if(authenticationException instanceof LockedException) { errorMessage = "賬號(hào)被鎖定"; }else{ errorMessage = "未知異常"; } //設(shè)置響應(yīng)編碼 httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); out.write(errorMessage); } }
3.3 403無(wú)權(quán)限處理類
package com.pjb.handler; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * 403無(wú)權(quán)限處理類 */ @Component public class PermissionDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); out.write("403無(wú)權(quán)限"); } }
4、Spring Security 的核心配置類
創(chuàng)建 WebSecurityConfig 類(Spring Security 配置類),并添加 @EnableWebSecurity 注解和繼承 WebSecurityConfigurerAdapter 類。
package com.pjb.config; import com.pjb.handler.LoginFailureHandler; import com.pjb.handler.LoginSuccessHandler; import com.pjb.handler.PermissionDeniedHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * Spring Security 配置類 * @author pan_junbiao **/ @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private LoginSuccessHandler loginSuccessHandler; @Autowired private LoginFailureHandler loginFailureHandler; @Autowired private PermissionDeniedHandler permissionDeniedHandler; //自定義的擴(kuò)展 UserDetailsService 類 @Autowired private UserDetailsService loginUserDetailsService; //自定義的擴(kuò)展 AuthenticationProvider 類 @Autowired private AuthenticationProvider loginAuthenticationProvider; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() //返回一個(gè)URL攔截注冊(cè)器 .anyRequest() //匹配所有的請(qǐng)求 .authenticated() //所有匹配的URL都需要被認(rèn)證才能訪問(wèn) .and() //結(jié)束當(dāng)前標(biāo)簽,讓上下文回到 HttpSecurity .formLogin() //啟動(dòng)表單認(rèn)證 .loginPage("/myLogin.html") //自定義登錄頁(yè)面 .loginProcessingUrl("/auth/form") //指定處理登錄請(qǐng)求路徑 .permitAll() //使登錄頁(yè)面不設(shè)限訪問(wèn) //.defaultSuccessUrl("/index") //登錄認(rèn)證成功后的跳轉(zhuǎn)頁(yè)面 .successHandler(loginSuccessHandler) //指定登錄成功時(shí)的處理 .failureHandler(loginFailureHandler) //指定登錄失敗時(shí)的處理 .and() .exceptionHandling().accessDeniedHandler(permissionDeniedHandler) //403無(wú)權(quán)時(shí)的返回操作 .and().csrf().disable(); //關(guān)閉CSRF的防御功能 } /** * 核心代碼: * 將擴(kuò)展的 LoginUserDetailsService 和 LoginAuthenticationProvider 注入, * 并將其添加到 AuthenticationManagerBuilder 中,這樣 AuthenticationManagerBuilder 將基于上述 * 自定義的 LoginUserDetailsService 來(lái)完成 UserDetails 的創(chuàng)建和管理, * 并基于自定義的 LoginAuthenticationProvider 完成用戶認(rèn)證。 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(loginUserDetailsService); auth.authenticationProvider(loginAuthenticationProvider); } /** * 由于5.x版本之后默認(rèn)啟用了委派密碼編譯器, * 因而按照以往的方式設(shè)置內(nèi)存密碼將會(huì)讀取異常, * 所以需要暫時(shí)將密碼編碼器設(shè)置為 NoOpPasswordEncoder */ @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } }
將擴(kuò)展的 LoginUserDetailsService 和 LoginAuthenticationProvider 注入,并將其添加到 AuthenticationManagerBuilder 中,這樣 AuthenticationManagerBuilder 將基于上述自定義的 LoginUserDetailsService 來(lái)完成 UserDetails 的創(chuàng)建和管理,并基于自定義的 LoginAuthenticationProvider 完成用戶認(rèn)證。
5、前端頁(yè)面
5.1 控制器層
創(chuàng)建 IndexController 類(首頁(yè)控制器),實(shí)現(xiàn)獲取當(dāng)前登錄用戶名并跳轉(zhuǎn)至首頁(yè)。
package com.pjb.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import java.security.Principal; /** * 首頁(yè)控制器 * @author pan_junbiao **/ @Controller public class IndexController { /** * 首頁(yè) */ @RequestMapping("/") public String index(HttpServletRequest request) { //獲取當(dāng)前登錄人 String userName = "未登錄"; Principal principal = request.getUserPrincipal(); if (principal != null) { userName = principal.getName(); } //返回頁(yè)面 request.setAttribute("userName", userName); return "/index.html"; } }
5.2 編寫(xiě)登錄頁(yè)面
在 resources\static 靜態(tài)資源目錄下,創(chuàng)建 myLogin.html 頁(yè)面。
注意:myLogin.html 頁(yè)面必須放在 resources\static 靜態(tài)資源目錄下,否則頁(yè)面無(wú)法加載。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登錄</title> <meta name="author" content="pan_junbiao的博客"> </head> <body> <form name="myForm" action="/auth/form" method="post"> <table align="center"> <caption>用戶登錄</caption> <tr> <td>登錄賬戶:</td> <td> <input type="text" name="username" placeholder="請(qǐng)輸入登錄賬戶" value="panjunbiao" /> </td> </tr> <tr> <td>登錄密碼:</td> <td> <input type="password" name="password" placeholder="請(qǐng)輸入登錄密碼" value="123456" /> </td> </tr> <!-- 以下是提交、取消按鈕 --> <tr> <td colspan="2" style="text-align: center; padding: 5px;"> <input type="submit" value="提交" /> <input type="reset" value="重置" /> </td> </tr> </table> </form> </body> </html>
5.3 編寫(xiě)首頁(yè)
在 resources\templates 資源目錄下,創(chuàng)建 index.html 頁(yè)面。
注意:首頁(yè) index.html 頁(yè)面中使用 Thymeleaf 模板 。
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>首頁(yè)</title> <meta name="author" content="pan_junbiao的博客"> </head> <body> <h1 style="color: red">Hello,Spring Security</h1> <p>博客信息:您好,歡迎訪問(wèn) pan_junbiao的博客</p> <p>博客地址:https://blog.csdn.net/pan_junbiao</p> <p th:text="'當(dāng)前登錄人:' + ${userName}"></p> <a href="/logout" rel="external nofollow" onclick="return confirm('確認(rèn)注銷嗎?');">登出</a> </body> </html>
6、運(yùn)行項(xiàng)目
6.1 登錄頁(yè)面
6.2 登錄成功后,跳轉(zhuǎn)至首頁(yè)
以上就是SpringSecurity實(shí)現(xiàn)自定義用戶認(rèn)證方案的詳細(xì)內(nèi)容,更多關(guān)于SpringSecurity自定義用戶認(rèn)證的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot配置多數(shù)據(jù)源并集成Druid和mybatis的操作
這篇文章主要介紹了springboot配置多數(shù)據(jù)源并集成Druid和mybatis的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java實(shí)現(xiàn)手寫(xiě)線程池實(shí)例并測(cè)試詳解
這篇文章主要來(lái)模擬一下線程池和工作隊(duì)列的流程,以及編寫(xiě)代碼和測(cè)試類進(jìn)行測(cè)試。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-02-02java使用Jsoup連接網(wǎng)站超時(shí)的解決方法
jsoup是一個(gè)非常好的解析網(wǎng)頁(yè)的包,用java開(kāi)發(fā)的,提供了類似DOM,CSS選擇器的方式來(lái)查找和提取文檔中的內(nèi)容,提取文檔內(nèi)容時(shí)會(huì)出現(xiàn)超時(shí)的情況,解決方法可看下文2013-11-11使用SpringBoot創(chuàng)建一個(gè)RESTful API的詳細(xì)步驟
使用 Java 的 Spring Boot 創(chuàng)建 RESTful API 可以滿足多種開(kāi)發(fā)場(chǎng)景,它提供了快速開(kāi)發(fā)、易于配置、可擴(kuò)展、可維護(hù)的優(yōu)點(diǎn),尤其適合現(xiàn)代軟件開(kāi)發(fā)的需求,幫助你快速構(gòu)建出高性能的后端服務(wù),需要的朋友可以參考下2025-01-01Java數(shù)據(jù)結(jié)構(gòu)與算法之循環(huán)隊(duì)列的實(shí)現(xiàn)
循環(huán)隊(duì)列 (Circular Queue) 是一種特殊的隊(duì)列。循環(huán)隊(duì)列解決了隊(duì)列出隊(duì)時(shí)需要將所有數(shù)據(jù)前移一位的問(wèn)題。本文將帶大家詳細(xì)了解循環(huán)隊(duì)列如何實(shí)現(xiàn),需要的朋友可以參考一下2021-12-12Java使用Collections.sort()排序的示例詳解
這篇文章主要介紹了Java使用Collections.sort()排序的示例詳解,Collections.sort(list, new PriceComparator());的第二個(gè)參數(shù)返回一個(gè)int型的值,就相當(dāng)于一個(gè)標(biāo)志,告訴sort方法按什么順序來(lái)對(duì)list進(jìn)行排序。對(duì)此感興趣的可以了解一下2020-07-07Springboot2.x+Quartz分布式集群的實(shí)現(xiàn)
這篇文章主要介紹了Springboot2.x+Quartz分布式集群的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java JVM原理與調(diào)優(yōu)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫(xiě),JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),是通過(guò)在實(shí)際的計(jì)算機(jī)上仿真模擬各種計(jì)算機(jī)功能來(lái)實(shí)現(xiàn)的。下面通過(guò)本文給大家介紹jvm原理與調(diào)優(yōu)相關(guān)知識(shí),感興趣的朋友一起學(xué)習(xí)吧2017-04-04Java中的相除(/)和取余(%)的實(shí)現(xiàn)方法
這篇文章主要介紹了Java中的相除(/)和取余(%)的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07java實(shí)現(xiàn)順序結(jié)構(gòu)線性列表的函數(shù)代碼
java實(shí)現(xiàn)順序結(jié)構(gòu)線性列表的函數(shù)代碼。需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-10-10