spring security如何擴(kuò)展自定義登錄
背景
spring security 默認(rèn)給我們提供了一套用戶名密碼登錄的的邏輯實(shí)現(xiàn),在很多時候根本不滿足我們實(shí)際開發(fā)的要求,所以我們經(jīng)常需要對它認(rèn)證模塊進(jìn)行擴(kuò)展,那么如何擴(kuò)展我們的認(rèn)證方式呢,這篇文章將會給你一個較為完整的解答。
認(rèn)證原理
Spring Security使用了一種基于過濾器鏈的認(rèn)證原理來保護(hù)應(yīng)用程序。它提供了一組過濾器,這些過濾器按照特定的順序?qū)γ總€請求進(jìn)行處理。
認(rèn)證過程中的主要步驟如下:
- 用戶發(fā)送認(rèn)證請求到應(yīng)用程序。
- 應(yīng)用程序?qū)⒄埱蟀l(fā)送給Spring Security的過濾器鏈。
- 過濾器鏈依次處理請求,直到找到可以處理該請求的過濾器。
- 找到處理請求的過濾器后,該過濾器會驗(yàn)證用戶的憑證(如用戶名和密碼)。
- 如果憑證有效,則生成一個已認(rèn)證的安全上下文,并將其保存在Spring Security的上下文存儲中。
- 如果憑證無效,則返回認(rèn)證失敗的信息給用戶。
- 認(rèn)證成功后,用戶可以繼續(xù)訪問受保護(hù)的資源。
在認(rèn)證過程中,Spring Security還提供了靈活的配置選項,可以根據(jù)特定的需求進(jìn)行自定義配置,例如使用不同的認(rèn)證提供程序、自定義身份驗(yàn)證邏輯等。
總結(jié)起來,Spring Security的認(rèn)證原理是通過一組過濾器鏈來處理認(rèn)證請求,驗(yàn)證用戶憑證并生成認(rèn)證的安全上下文,以保護(hù)應(yīng)用程序的資源。
具體實(shí)現(xiàn)原理
Spring Security的認(rèn)證實(shí)現(xiàn)原理主要涉及以下幾個核心概念和組件:
- 用戶提供的憑證:用戶在進(jìn)行認(rèn)證時,需要提供其身份憑證,如用戶名和密碼。
- 認(rèn)證提供程序(Authentication Provider):認(rèn)證提供程序是Spring Security中的重要組件,負(fù)責(zé)驗(yàn)證用戶提供的憑證。它使用用戶提供的憑證與系統(tǒng)中存儲的憑證進(jìn)行比對,并決定是否通過認(rèn)證。Spring
- Security提供了多種內(nèi)置的認(rèn)證提供程序,如基于數(shù)據(jù)庫的JDBC認(rèn)證提供程序、LDAP認(rèn)證提供程序、InMemory認(rèn)證提供程序等。同時也支持自定義認(rèn)證提供程序。
- 用戶詳情服務(wù)(UserDetailsService):用戶詳情服務(wù)是Spring Security中的接口,負(fù)責(zé)獲取用戶的詳細(xì)信息,如用戶的權(quán)限、角色等。認(rèn)證提供程序在驗(yàn)證用戶憑證后,會使用用戶詳情服務(wù)獲取用戶的詳細(xì)信息,并構(gòu)建一個認(rèn)證對象(Authentication)。
- 認(rèn)證對象(Authentication):認(rèn)證對象是Spring Security中表示已認(rèn)證用戶的對象。它包含了用戶的身份信息、憑證信息、權(quán)限信息等。認(rèn)證對象由認(rèn)證提供程序生成,并將其保存在SecurityContextHolder的上下文存儲中。
- 認(rèn)證過濾器鏈(Authentication Filter Chain):認(rèn)證過濾器鏈?zhǔn)荢pring Security中的過濾器組成的鏈條,負(fù)責(zé)處理認(rèn)證請求。每個過濾器都會對請求進(jìn)行處理,并根據(jù)需要進(jìn)行認(rèn)證、授權(quán)和其他安全操作。在認(rèn)證過程中,認(rèn)證過濾器鏈會根據(jù)請求的URL匹配合適的過濾器進(jìn)行處理。
- 安全上下文(Security Context):安全上下文是Spring Security中用于保存已認(rèn)證用戶信息的容器。它以ThreadLocal的方式存儲,可以通過SecurityContextHolder來訪問和操作。安全上下文中保存了當(dāng)前的認(rèn)證對象,可以在應(yīng)用程序的任何地方獲取已認(rèn)證用戶的信息。
總結(jié)起來,Spring Security的認(rèn)證實(shí)現(xiàn)原理是通過認(rèn)證提供程序驗(yàn)證用戶提供的憑證,使用用戶詳情服務(wù)獲取用戶的詳細(xì)信息,并構(gòu)建一個認(rèn)證對象,然后將該認(rèn)證對象保存在安全上下文中。
認(rèn)證過濾器鏈負(fù)責(zé)處理認(rèn)證請求,并根據(jù)需要進(jìn)行認(rèn)證、授權(quán)和其他安全操作。
通過這種方式,Spring Security實(shí)現(xiàn)了對應(yīng)用程序進(jìn)行認(rèn)證和授權(quán)的功能。
如何實(shí)現(xiàn)自定義認(rèn)證的功能
要自定義Spring Security的認(rèn)證模塊,你可以按照以下步驟進(jìn)行:
- 創(chuàng)建一個自定義的認(rèn)證提供程序(Authentication Provider)類,該類實(shí)現(xiàn)了
org.springframework.security.authentication.AuthenticationProvider
接口。在該類中,你可以編寫你自己的認(rèn)證邏輯,比如驗(yàn)證用戶名和密碼是否匹配。 - 實(shí)現(xiàn)
UserDetailsService
接口來獲取用戶的詳細(xì)信息。你可以根據(jù)自己的需求,從數(shù)據(jù)庫、LDAP等數(shù)據(jù)源中獲取用戶信息,并返回一個UserDetails
對象。 - 在配置類(通常是繼承自
WebSecurityConfigurerAdapter
的類)中,覆蓋configure(AuthenticationManagerBuilder auth)
方法。在該方法中,你可以指定使用你自定義的認(rèn)證提供程序和用戶詳情服務(wù)來進(jìn)行認(rèn)證。 - 在上述配置類中,覆蓋
configure(HttpSecurity http)
方法來配置認(rèn)證過濾器鏈。你可以根據(jù)需要添加和配置各種過濾器,例如表單登錄過濾器、基于Token的認(rèn)證過濾器等。
下面根據(jù)微信小程序登錄來實(shí)現(xiàn)一個自定義模塊的接口:
定義token信息類
public class JsCodeAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 5615450055779559101L; private String code; /** * Creates a token with the supplied array of authorities. * * @param authorities the collection of <tt>GrantedAuthority</tt>s for the principal * represented by this authentication object. */ public JsCodeAuthenticationToken(Collection<? extends GrantedAuthority> authorities, String code) { super(authorities); this.code = code; } public JsCodeAuthenticationToken(String code) { this(Collections.emptyList(), code); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return code; } public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException( "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); } }
從代碼中我們可以看出我們需要創(chuàng)建一個相關(guān)的token類來繼承 AbstractAuthenticationToken 類,并實(shí)現(xiàn)相關(guān)的方法
AbstractAuthenticationToken 的定義
- Spring Security框架中的一個抽象類,用于表示身份驗(yàn)證請求令牌。它用于封裝用戶憑據(jù)或其他必要的信息來進(jìn)行用戶身份驗(yàn)證。
- AbstractAuthenticationToken的子類負(fù)責(zé)提供特定的身份驗(yàn)證細(xì)節(jié),例如用戶名和密碼、基于令牌的身份驗(yàn)證令牌等。這些子類通常包含獲取器和設(shè)置器,用于訪問和修改身份驗(yàn)證細(xì)節(jié)。
- AbstractAuthenticationToken還提供了一組方法來管理身份驗(yàn)證狀態(tài),例如設(shè)置已認(rèn)證標(biāo)志、檢索與已認(rèn)證用戶關(guān)聯(lián)的權(quán)限以及管理與身份驗(yàn)證過程相關(guān)的其他詳細(xì)信息。
AbstractAuthenticationToken 的工作原理
- 當(dāng)用戶請求進(jìn)行身份驗(yàn)證時,Spring Security將創(chuàng)建一個實(shí)現(xiàn)AbstractAuthenticationToken的具體子類對象,并將其中的身份驗(yàn)證信息進(jìn)行填充。然后,該身份驗(yàn)證令牌將被傳遞給身份驗(yàn)證管理器進(jìn)行進(jìn)一步的身份驗(yàn)證處理。
- 身份驗(yàn)證管理器將檢查身份驗(yàn)證令牌,并根據(jù)所配置的身份驗(yàn)證策略和安全規(guī)則,對用戶進(jìn)行身份驗(yàn)證。在身份驗(yàn)證過程中,身份驗(yàn)證管理器可能會調(diào)用相關(guān)的身份驗(yàn)證提供者,比如用戶名密碼身份驗(yàn)證提供者或令牌身份驗(yàn)證提供者,來驗(yàn)證用戶的憑據(jù)信息。
- 一旦用戶通過身份驗(yàn)證,身份驗(yàn)證管理器將更新AbstractAuthenticationToken中的已認(rèn)證標(biāo)志和權(quán)限信息,并將其返回給應(yīng)用程序。在后續(xù)的請求中,應(yīng)用程序可以根據(jù)需要訪問身份驗(yàn)證令牌中的身份驗(yàn)證信息和權(quán)限信息,以實(shí)現(xiàn)不同的業(yè)務(wù)邏輯和訪問控制。
定義一個接收參數(shù)的filter
@Slf4j public class JsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static String jsCode = "code"; private static boolean postOnly = true; protected JsCodeAuthenticationFilter(WechatProperties properties) { super(new AntPathRequestMatcher(properties.getAuthUrl(), "POST")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { if (postOnly && !request.getMethod().equalsIgnoreCase("post")) { throw new AuthenticationServiceException("該接口不支持:" + request.getMethod()); } String code = obtainCode(request); code = code.trim(); log.info("得到的code:{}", code); JsCodeAuthenticationToken authenticationTokenRequest = new JsCodeAuthenticationToken(code); setDetails(request, authenticationTokenRequest); return this.getAuthenticationManager().authenticate(authenticationTokenRequest); } public String obtainCode(HttpServletRequest request) { if (request.getHeader("Content-Type").contains("application/json")) { return obtainCodeJson(request, jsCode); } return request.getParameter(jsCode); } public String obtainCodeJson(HttpServletRequest request, String param) { StringBuilder builder = new StringBuilder(); String line = null; String code = null; try{ BufferedReader reader = request.getReader(); while ((line = reader.readLine()) != null) { builder.append(line); } Map<String, String> map = JsonUtils.fromString(builder.toString(), Map.class, String.class, String.class); code = map.get(param); }catch (RuntimeException | IOException e) { throw new AuthenticationServiceException("獲取參數(shù)失敗"); } return code; } /** * Provided so that subclasses may configure what is put into the * authentication request's details property. * * @param request that an authentication request is being created for * @param authRequest the authentication request object that should have its details * set */ protected void setDetails(HttpServletRequest request, JsCodeAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); }
從上面的代碼可以看出,我們需要定義一個filter 類繼承 AbstractAuthenticationProcessingFilter 來獲取前端傳輸?shù)膮?shù)
AbstractAuthenticationProcessingFilter的定義
- AbstractAuthenticationProcessingFilter是Spring Security框架中的一個過濾器,用于處理用戶認(rèn)證的相關(guān)操作。
AbstractAuthenticationProcessingFilter的功能有哪些
- 攔截用戶發(fā)起的認(rèn)證請求,并交給AuthenticationManager來進(jìn)行身份驗(yàn)證。
- 通過調(diào)用AuthenticationManager完成身份驗(yàn)證后,將認(rèn)證結(jié)果封裝成Authentication對象。
- 將認(rèn)證結(jié)果傳遞給AbstractAuthenticationProcessingFilter的成功或失敗處理器進(jìn)行處理。
- 處理認(rèn)證成功或失敗的邏輯,例如生成并返回認(rèn)證成功的Jwt token、跳轉(zhuǎn)到特定頁面、返回錯誤信息等。
定義相關(guān)的Provider 類
public class JsCodeAuthenticationProvider implements AuthenticationProvider { private WxOAuth2Service weChatService; private UserDetailsService userDetailsService; private WxMpProperties wxMpProperties; public JsCodeAuthenticationProvider(WxOAuth2Service weChatService, WxMpProperties wxMpProperties) { this.weChatService = weChatService; this.wxMpProperties = wxMpProperties; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { JsCodeAuthenticationToken token = (JsCodeAuthenticationToken) authentication; String code = (String) token.getPrincipal(); log.info("得到的code:{}", code); if (StringUtils.isBlank(code)) { throw new AuthenticationServiceException("jscode 不能為空"); } WxOAuth2AccessToken wxOAuth2AccessToken; try { wxOAuth2AccessToken = weChatService.getAccessToken(wxMpProperties.getAppId(), wxMpProperties.getSecret(), code); } catch (WxErrorException e) { throw new AuthenticationServiceException("授權(quán)登錄失敗"); } WeChatDetailsService weChatDetailsService = (WeChatDetailsService)userDetailsService; UserDetails details = weChatDetailsService.loadByToken(wxOAuth2AccessToken.getOpenId(), wxOAuth2AccessToken.getAccessToken()); if (details == null) { throw new AuthenticationServiceException("無法獲取公眾號用戶"); } JsCodeAuthenticationToken authenticationResult = new JsCodeAuthenticationToken(details.getUsername()); authenticationResult.setDetails(token.getDetails()); return authenticationResult; } @Override public boolean supports(Class<?> authentication) { return JsCodeAuthenticationToken.class.isAssignableFrom(authentication); } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } }
AuthenticationProvider 的定義
AuthenticationProvider是Spring Security框架中的一個接口,用于身份驗(yàn)證的核心組件。
它的定義如下:
public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); }
它包含兩個方法:
- authenticate(Authentication authentication):該方法用于執(zhí)行身份驗(yàn)證過程。輸入?yún)?shù)authentication是一個封裝了用戶認(rèn)證信息的Authentication對象,包括用戶名、密碼等信息。該方法返回一個認(rèn)證成功的Authentication對象,或者拋出AuthenticationException異常表示認(rèn)證失敗。
- supports(Class<?> authentication):該方法用于判斷是否支持給定類型的認(rèn)證請求。輸入?yún)?shù)authentication是要被驗(yàn)證的對象類型,通常是UsernamePasswordAuthenticationToken或JwtAuthenticationToken等。該方法返回一個boolean值,表示是否支持該類型的認(rèn)證請求。
AuthenticationProvider可以自定義實(shí)現(xiàn),用于實(shí)現(xiàn)不同的身份驗(yàn)證邏輯。開發(fā)者可以通過實(shí)現(xiàn)該接口,并覆蓋其中的方法來定制化身份驗(yàn)證過程,例如從數(shù)據(jù)庫查詢用戶信息并進(jìn)行密碼校驗(yàn)等。
在Spring Security的配置中,可以通過AuthenticationManagerBuilder來注冊和配置AuthenticationProvider的實(shí)例,以供身份驗(yàn)證使用。
定義相關(guān)配置類
@Component @EnableConfigurationProperties(WxMpProperties.class) public class JsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Autowired private UserDetailsService userDetailsService; @Autowired private AuthenticationSuccessHandler jsCodeAuthenticationSuccessHandler; @Autowired private AuthenticationFailureHandler jsCodeAuthenticationFailureHandler; @Autowired private WxOAuth2Service weChatService; @Autowired private WechatProperties weChatProperties; @Autowired private WxMpProperties wxMpProperties; @Override public void configure(HttpSecurity http) throws Exception { JsCodeAuthenticationFilter mobilePasswordAuthenticationFilter = new JsCodeAuthenticationFilter(weChatProperties); mobilePasswordAuthenticationFilter.setAuthenticationSuccessHandler(jsCodeAuthenticationSuccessHandler); mobilePasswordAuthenticationFilter.setAuthenticationFailureHandler(jsCodeAuthenticationFailureHandler); mobilePasswordAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); JsCodeAuthenticationProvider provider = new JsCodeAuthenticationProvider(weChatService, wxMpProperties); provider.setUserDetailsService(userDetailsService); // 將當(dāng)前服務(wù)注冊到 mobilePasswordAuthenticationFilter 連之后 http.authenticationProvider(provider).addFilterAfter(mobilePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
SecurityConfigurerAdapter 的定義
SecurityConfigurerAdapter是Spring Security提供的一個抽象類,用于簡化配置和定制化Spring Security的行為。
它是一個適配器模式,繼承自SecurityConfigurer接口,并提供了一些默認(rèn)實(shí)現(xiàn)方法,用于提供給開發(fā)者靈活地配置和擴(kuò)展Spring Security。
SecurityConfigurerAdapter的定義如下:
public abstract class SecurityConfigurerAdapter<O extends SecurityFilterChain, B extends SecurityBuilder<O>> implements SecurityConfigurer<O, B> { // 提供一個空實(shí)現(xiàn)的configure方法,供子類進(jìn)行覆蓋定制 public void configure(B builder) throws Exception {} // 提供一個空實(shí)現(xiàn)的init方法,供子類進(jìn)行覆蓋定制 public void init(B builder) throws Exception {} // 提供一個空實(shí)現(xiàn)的configure方法,供子類進(jìn)行覆蓋定制 public void configure(O object) throws Exception {} // 提供一個空實(shí)現(xiàn)的postProcess方法,供子類進(jìn)行覆蓋定制 protected void postProcess(O object) throws Exception {} }
開發(fā)者可以繼承SecurityConfigurerAdapter,并重寫其中的方法來實(shí)現(xiàn)自定義的安全配置。常見的用法是在WebSecurityConfigurerAdapter中繼承SecurityConfigurerAdapter,并重寫configure方法來配置Spring Security的行為,例如定義認(rèn)證規(guī)則、權(quán)限控制等。通過繼承SecurityConfigurerAdapter可以避免直接實(shí)現(xiàn)SecurityConfigurer接口時需要實(shí)現(xiàn)所有方法的繁瑣操作。
整體配置
public void configure(HttpSecurity http) throws Exception { String[] auth = StringUtils.split( securityProperties.getAuthUrl(), ","); http.formLogin().failureHandler(authenticationFailureHandler) .successHandler(authenticationSuccessHandler) .loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM) .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL) .and().apply(jsCodeAuthenticationSecurityConfig) .and().apply(usernameAuthenticationSecurityConfig()) .and().authorizeRequests().antMatchers(auth).authenticated() .and().authorizeRequests().anyRequest().permitAll() .and().csrf().disable(); }
總結(jié)
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章

常用的ResponseEntity.BodyBuilder和自定義ResponseEntity的實(shí)例

深入了解SpringBoot中@ControllerAdvice的介紹及三種用法

Mybatis的TypeHandler加解密數(shù)據(jù)實(shí)現(xiàn)

java出現(xiàn)no XXX in java.library.path的解決及eclipse配

java靜態(tài)工具類注入service出現(xiàn)NullPointerException異常處理

解決Mybatis-Plus操作分頁后數(shù)據(jù)失效問題