本篇内容主要讲解“如何使用SpringSecurity扩展与配置”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何使用SpringSecurity扩展与配置”吧!
SpringSecurity原理(一)——初探 SpringSecurity原理(二)——认证 SpringSecurity原理(三)——授权 SpringSecurity原理(四)——过滤器 SpringSecurity原理(五)——扩展与配置
自定义Filter应该是最常用的需求了,例如,为了拦截大多数的暴力登录,我们一般会在登录的时候给一个验证码,但是UsernamePasswordAuthenticationFilter没有提供验证码的校验,所以我们就可以自定义一个Filter来处理验证码。
又如,对于前后端分离项目,我们更多使用Token,而不是Session,也可以通过Filter来处理Token的校验,续期的问题。
自定义Filter的方式有很多:
直接实现Filter
继承GenericFilterBean
继承OncePerRequestFilter重写doFilterInternal
继承BasicAuthenticationFilter重写doFilterInternal
继承AbstractAuthenticationProcessingFilter重写attemptAuthentication
继承UsernamePasswordAuthenticationFilter重写attemptAuthentication
……
后3个都是认证相关的Filter。
因为涉及到转发重定义等问题,一次请求Filter可能不止一次被调用,OncePerRequestFilter就是为了解决这个问题,它保证一次请求继承它的Filter只会被调用一次。
BasicAuthenticationFilter本身继承了OncePerRequestFilter,所以不用自己处理因为转发等引起的Filter多次调用问题。
AbstractAuthenticationProcessingFilter添加了认证失败,认证成功等处理,但是它没有处理一次请求可能多次调用的问题。
对于表单认证,想偷懒,可以自己继承UsernamePasswordAuthenticationFilter,例如,继承UsernamePasswordAuthenticationFilter,先处理验证码问题,如果校验成功,再调用UsernamePasswordAuthenticationFilter的attemptAuthentication方法。
反正自定义Filter非常灵活,根据自己的喜好选择。
自定义了Filter如何配置呢?
最简单的方式,自定义配置类重写WebSecurityConfigurerAdapter的configure方法:
@Override protected void configure(HttpSecurity http) { http.addFilter(zzzFilter) .addFilterAfter(aaaFilter) .addFilterBefore(yyyFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAt(xxxFilter,UsernamePasswordAuthenticationFilter.class); }
addFilter是添加到最后,但并不是最终的最后,因为后面的流程还会添加其他Filter
addFilterAfter,添加在指定Filter之后
addFilterBefore,添加在指定Filter之前
addFilterAt,添加在指定Filter之前,不会覆盖和删除指定的Filter,感觉和addFilterBefore差不多
当然,也可以通过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()); } }
实现LogoutSuccessHandler接口,一般返回json数据,以便于前端给出提示信息。
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()); }
实现AuthenticationFailureHandler接口,一般会返回json数据,然后前端根据返回数据,自己决定提示信息和跳转。
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(); } }
实现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(); } }
当然,也可以通过继承SimpleUrlAuthenticationSuccessHandler的方式。
配置也是老方式:
@Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() .successHandler(loginSuccessHandler) .failureHandler(loginFailureHandler) }
实现AuthenticationEntryPoint接口,这个接口在ExceptionTranslationFilter这个Filter截取到认证异常之后被调用,一般就是跳转登录页,可以参考: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("用户名或者密码错误").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
实现AccessDeniedHandler接口,这个接口在ExceptionTranslationFilter截获到的授权异常之后被调用。
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("您没有操作权限,请联系管理员").getBytes("UTF-8")); outputStream.flush(); outputStream.close(); } }
可以实现Authentication,也可以继承AbstractAuthenticationToken。一般不需要,除非要自定义认证器。
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; } }
实现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;//认证流程 } @Override public boolean supports(Class<?> authentication) {//支持校验哪种认证凭证 return authentication.isAssignableFrom(JwtAuthenticationToken.class); } }
可以实现AccessDecisionVoter接口,也可以直接继承WebExpressionVoter之类的,看具体需求,一般不需要,除非要自己设计新的授权体系。
public class MyExpressionVoter extends WebExpressionVoter { @Override public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) { return 1 ;//-1反对,0弃权,1同意 } }
一般配置WebSecurity,主要是为了忽略静态资源校验。
@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"); } }
匹配规则使用的是:AntPathRequestMatcher
HttpSecurity可以配置的东西太多了,下面是一个示例,可以参考,注意很多是重复不必要的配置,只是为了展示可以配置的内容。
@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. // 配置登录页,登录用户名密码参数 formLogin().loginPage("/login") .passwordParameter("username").passwordParameter("password") // 配置登录接口,defaultSuccessUrl配置登录成功跳转,会跳转到来的路径,而successForwardUrl会跳转固定页面 .loginProcessingUrl("/do-login").defaultSuccessUrl("/loginsucess") // 登录失败处理 .failureForwardUrl("/fail").failureUrl("/failure").failureHandler(loginAuthenticationFailureHandler) .permitAll() // 配置特定url权限 .and().authorizeRequests().antMatchers("/admin").hasRole("admin") // 配置鉴权管理器 .anyRequest().authenticated().accessDecisionManager(accessDecisionManager()) // 配置登出,登录url,登出成功处理器 .and().logout().logoutUrl("/logout").logoutSuccessHandler(new JwtLogoutSuccessHandler()) // 关闭csrf .and().csrf().disable(); // 配置自定义过滤器 http.addFilterAt(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class); // 配置鉴权失败的处理器 http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler()); } }
public interface SecurityBuilder<O> { O build() throws Exception; }
就是构建一个特殊对象O的抽象,例如Filter。
WebSecurity就是一个为了创建Filter对象而设计。
HttpSecurity也是一个SecurityBuilder,不过它是为了创建DefaultSecurityFilterChain。
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> { void init(B builder) throws Exception; void configure(B builder) throws Exception; }
SecurityConfigurer目的主要有两个:
init,初始化builder,例如给一些filter设置参数
configure,配置builder,例如创建添加新的filter
SecurityConfigurer的适配器,提供了init,configure的空实现,并且添加了一个CompositeObjectPostProcessor后置处理器,主要是用来处理Filter。
最主要添加了一个disable方法,基本上很多Filter的configure类都继承了它。
public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> { }
WebSecurityConfigurer主要是实例化了类型参数,告诉大家,我就是配置生产Filter的的SecurityBuilder。
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> { }
WebSecurityConfigurerAdapter更进一步告诉大家,我的SecurityBuilder就是WebSecurity,生产的就是Filter。
当然WebSecurityConfigurerAdapter没有开空头支票,还提供了很多功能,我们自己要对SpringSecurity进行配置的话,基本都是继承WebSecurityConfigurerAdapter。
WebSecurity是一个SecurityBuilder<Filter>,所以它的主要职责之一就是创建Filter,重点关注它的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; } }
很标准的模板方法模式。
正真执行构建的逻辑在performBuild方法中,这个方法在WebSecurity创建了FilterChainProxy,在HttpSecurity中创建了DefaultSecurityFilterChain。
前面已经分析了,HttpSecurity的目的就一个创建DefaultSecurityFilterChain,注意它的performBuild方法。
到此,相信大家对“如何使用SpringSecurity扩展与配置”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。