您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“如何使用SpringSecurity擴(kuò)展與配置”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“如何使用SpringSecurity擴(kuò)展與配置”吧!
SpringSecurity原理(一)——初探 SpringSecurity原理(二)——認(rèn)證 SpringSecurity原理(三)——授權(quán) SpringSecurity原理(四)——過濾器 SpringSecurity原理(五)——擴(kuò)展與配置
自定義Filter應(yīng)該是最常用的需求了,例如,為了攔截大多數(shù)的暴力登錄,我們一般會(huì)在登錄的時(shí)候給一個(gè)驗(yàn)證碼,但是UsernamePasswordAuthenticationFilter沒有提供驗(yàn)證碼的校驗(yàn),所以我們就可以自定義一個(gè)Filter來處理驗(yàn)證碼。
又如,對(duì)于前后端分離項(xiàng)目,我們更多使用Token,而不是Session,也可以通過Filter來處理Token的校驗(yàn),續(xù)期的問題。
自定義Filter的方式有很多:
直接實(shí)現(xiàn)Filter
繼承GenericFilterBean
繼承OncePerRequestFilter重寫doFilterInternal
繼承BasicAuthenticationFilter重寫doFilterInternal
繼承AbstractAuthenticationProcessingFilter重寫attemptAuthentication
繼承UsernamePasswordAuthenticationFilter重寫attemptAuthentication
……
后3個(gè)都是認(rèn)證相關(guān)的Filter。
因?yàn)樯婕暗睫D(zhuǎn)發(fā)重定義等問題,一次請(qǐng)求Filter可能不止一次被調(diào)用,OncePerRequestFilter就是為了解決這個(gè)問題,它保證一次請(qǐng)求繼承它的Filter只會(huì)被調(diào)用一次。
BasicAuthenticationFilter本身繼承了OncePerRequestFilter,所以不用自己處理因?yàn)檗D(zhuǎn)發(fā)等引起的Filter多次調(diào)用問題。
AbstractAuthenticationProcessingFilter添加了認(rèn)證失敗,認(rèn)證成功等處理,但是它沒有處理一次請(qǐng)求可能多次調(diào)用的問題。
對(duì)于表單認(rèn)證,想偷懶,可以自己繼承UsernamePasswordAuthenticationFilter,例如,繼承UsernamePasswordAuthenticationFilter,先處理驗(yàn)證碼問題,如果校驗(yàn)成功,再調(diào)用UsernamePasswordAuthenticationFilter的attemptAuthentication方法。
反正自定義Filter非常靈活,根據(jù)自己的喜好選擇。
自定義了Filter如何配置呢?
最簡(jiǎn)單的方式,自定義配置類重寫WebSecurityConfigurerAdapter的configure方法:
@Override protected void configure(HttpSecurity http) { http.addFilter(zzzFilter) .addFilterAfter(aaaFilter) .addFilterBefore(yyyFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAt(xxxFilter,UsernamePasswordAuthenticationFilter.class); }
addFilter是添加到最后,但并不是最終的最后,因?yàn)楹竺娴牧鞒踢€會(huì)添加其他Filter
addFilterAfter,添加在指定Filter之后
addFilterBefore,添加在指定Filter之前
addFilterAt,添加在指定Filter之前,不會(huì)覆蓋和刪除指定的Filter,感覺和addFilterBefore差不多
當(dāng)然,也可以通過SecurityConfigurerAdapter的方式:
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { @Override public void configure(HttpSecurity http) { JwtAuthenticationFilter filter = new JwtAuthenticationFilter(); http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); } }
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests((requests) -> requests.anyRequest().authenticated()); http.formLogin(); http.httpBasic(); http.apply(new JwtConfigurer()); } }
實(shí)現(xiàn)LogoutSuccessHandler接口,一般返回json數(shù)據(jù),以便于前端給出提示信息。
public class JwtLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { if (authentication != null) { new SecurityContextLogoutHandler().logout(request, response, authentication); } response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write("jwt loginout success").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
配置方式:
protected void configure(HttpSecurity http){ http..logout() .logoutSuccessHandler(new JwtLogoutSuccessHandler()); }
實(shí)現(xiàn)AuthenticationFailureHandler接口,一般會(huì)返回json數(shù)據(jù),然后前端根據(jù)返回?cái)?shù)據(jù),自己決定提示信息和跳轉(zhuǎn)。
public class LoginFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(JSONUtil.toJsonStr("登錄失敗").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
實(shí)現(xiàn)AuthenticationSuccessHandler接口,如果有生成token的邏輯可以放在這里面。
public class LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); ServletOutputStream outputStream = response.getOutputStream(); // 生成保存jwt等邏輯可以放這里 outputStream.write(JSONUtil.toJsonStr("登錄成功").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
當(dāng)然,也可以通過繼承SimpleUrlAuthenticationSuccessHandler的方式。
配置也是老方式:
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler) }
實(shí)現(xiàn)AuthenticationEntryPoint接口,這個(gè)接口在ExceptionTranslationFilter這個(gè)Filter截取到認(rèn)證異常之后被調(diào)用,一般就是跳轉(zhuǎn)登錄頁,可以參考:LoginUrlAuthenticationEntryPoint
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(JSONUtil.toJsonStr("用戶名或者密碼錯(cuò)誤").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
實(shí)現(xiàn)AccessDeniedHandler接口,這個(gè)接口在ExceptionTranslationFilter截獲到的授權(quán)異常之后被調(diào)用。
public class JwtAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write(JSONUtil.toJsonStr("您沒有操作權(quán)限,請(qǐng)聯(lián)系管理員").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
可以實(shí)現(xiàn)Authentication,也可以繼承AbstractAuthenticationToken。一般不需要,除非要自定義認(rèn)證器。
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; public class JwtAuthenticationToken extends AbstractAuthenticationToken { public JwtAuthenticationToken(Collection<? extends GrantedAuthority> authorities) { super(authorities); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return null; } }
實(shí)現(xiàn)AuthenticationProvider接口。
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; public class JwtAuthenticationProvider implements AuthenticationProvider { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { return null;//認(rèn)證流程 } @Override public boolean supports(Class<?> authentication) {//支持校驗(yàn)?zāi)姆N認(rèn)證憑證 return authentication.isAssignableFrom(JwtAuthenticationToken.class); } }
可以實(shí)現(xiàn)AccessDecisionVoter接口,也可以直接繼承WebExpressionVoter之類的,看具體需求,一般不需要,除非要自己設(shè)計(jì)新的授權(quán)體系。
public class MyExpressionVoter extends WebExpressionVoter { @Override public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) { return 1 ;//-1反對(duì),0棄權(quán),1同意 } }
一般配置WebSecurity,主要是為了忽略靜態(tài)資源校驗(yàn)。
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(WebSecurity web) { web.ignoring().antMatchers( "/**/*.html", "/public/**/*.js", "/public/**/*.css", "/public/**/*.png", "/**/*.gif", "/**/*.png", "/**/*.jpg", "/**/*.ico"); } }
匹配規(guī)則使用的是:AntPathRequestMatcher
HttpSecurity可以配置的東西太多了,下面是一個(gè)示例,可以參考,注意很多是重復(fù)不必要的配置,只是為了展示可以配置的內(nèi)容。
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { public AccessDecisionManager accessDecisionManager(){ List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList( new WebExpressionVoter(), new RoleVoter(), new AuthenticatedVoter()); return new ConsensusBased(decisionVoters); } @Override protected void configure(HttpSecurity http) throws Exception { http. // 配置登錄頁,登錄用戶名密碼參數(shù) formLogin().loginPage("/login") .passwordParameter("username").passwordParameter("password") // 配置登錄接口,defaultSuccessUrl配置登錄成功跳轉(zhuǎn),會(huì)跳轉(zhuǎn)到來的路徑,而successForwardUrl會(huì)跳轉(zhuǎn)固定頁面 .loginProcessingUrl("/do-login").defaultSuccessUrl("/loginsucess") // 登錄失敗處理 .failureForwardUrl("/fail").failureUrl("/failure").failureHandler(loginAuthenticationFailureHandler) .permitAll() // 配置特定url權(quán)限 .and().authorizeRequests().antMatchers("/admin").hasRole("admin") // 配置鑒權(quán)管理器 .anyRequest().authenticated().accessDecisionManager(accessDecisionManager()) // 配置登出,登錄url,登出成功處理器 .and().logout().logoutUrl("/logout").logoutSuccessHandler(new JwtLogoutSuccessHandler()) // 關(guān)閉csrf .and().csrf().disable(); // 配置自定義過濾器 http.addFilterAt(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class); // 配置鑒權(quán)失敗的處理器 http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler()); } }
public interface SecurityBuilder<O> { O build() throws Exception; }
就是構(gòu)建一個(gè)特殊對(duì)象O的抽象,例如Filter。
WebSecurity就是一個(gè)為了創(chuàng)建Filter對(duì)象而設(shè)計(jì)。
HttpSecurity也是一個(gè)SecurityBuilder,不過它是為了創(chuàng)建DefaultSecurityFilterChain。
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> { void init(B builder) throws Exception; void configure(B builder) throws Exception; }
SecurityConfigurer目的主要有兩個(gè):
init,初始化builder,例如給一些filter設(shè)置參數(shù)
configure,配置builder,例如創(chuàng)建添加新的filter
SecurityConfigurer的適配器,提供了init,configure的空實(shí)現(xiàn),并且添加了一個(gè)CompositeObjectPostProcessor后置處理器,主要是用來處理Filter。
最主要添加了一個(gè)disable方法,基本上很多Filter的configure類都繼承了它。
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> { }
WebSecurityConfigurer主要是實(shí)例化了類型參數(shù),告訴大家,我就是配置生產(chǎn)Filter的的SecurityBuilder。
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> { }
WebSecurityConfigurerAdapter更進(jìn)一步告訴大家,我的SecurityBuilder就是WebSecurity,生產(chǎn)的就是Filter。
當(dāng)然WebSecurityConfigurerAdapter沒有開空頭支票,還提供了很多功能,我們自己要對(duì)SpringSecurity進(jìn)行配置的話,基本都是繼承WebSecurityConfigurerAdapter。
WebSecurity是一個(gè)SecurityBuilder<Filter>,所以它的主要職責(zé)之一就是創(chuàng)建Filter,重點(diǎn)關(guān)注它的build方法,是繼承了AbstractSecurityBuilder的build,具體邏輯在AbstractConfiguredSecurityBuilder的doBuild方法中。
@Override protected final O doBuild() throws Exception { synchronized (this.configurers) { this.buildState = BuildState.INITIALIZING; beforeInit(); init(); this.buildState = BuildState.CONFIGURING; beforeConfigure(); configure(); this.buildState = BuildState.BUILDING; O result = performBuild(); this.buildState = BuildState.BUILT; return result; } }
很標(biāo)準(zhǔn)的模板方法模式。
正真執(zhí)行構(gòu)建的邏輯在performBuild方法中,這個(gè)方法在WebSecurity創(chuàng)建了FilterChainProxy,在HttpSecurity中創(chuàng)建了DefaultSecurityFilterChain。
前面已經(jīng)分析了,HttpSecurity的目的就一個(gè)創(chuàng)建DefaultSecurityFilterChain,注意它的performBuild方法。
到此,相信大家對(duì)“如何使用SpringSecurity擴(kuò)展與配置”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。