相关文章推荐
慷慨的铁板烧  ·  Java-Stream流和Lambda表达式·  1 年前    · 
仗义的铅笔  ·  关于Velocity模板引擎的一些思考总结 ...·  2 年前    · 
乐观的竹笋  ·  C# Excel操作 - 一杯水M - 博客园·  2 年前    · 
Code  ›  SpringSecurity原理(五)——扩展与配置_http.addfilterbefore_trayvontang的博客
https://blog.csdn.net/trayvontang/article/details/116492472
很拉风的石榴
2 年前
    • 简介
    • 自定义扩展
      • 自定义Filter
      • 自定义登出成功处理器
      • 认证失败处理器
      • AuthenticationSuccessHandler
      • 认证异常跳转入口
      • 授权异常处理器
      • 自定义认证凭证
      • 自定义认证器
      • 自定义投票者
    • 配置
      • 配置WebSecurity
      • 配置HttpSecurity
    • 几个重要的类与接口
      • SecurityBuilder
      • SecurityConfigurer
      • SecurityConfigurerAdapter
      • AbstractHttpConfigurer
      • WebSecurityConfigurer
      • WebSecurityConfigurerAdapter
      • WebSecurity
      • HttpSecurity

      SpringSecurity原理(一)——初探
      SpringSecurity原理(二)——认证
      SpringSecurity原理(三)——授权
      SpringSecurity原理(四)——过滤器
      SpringSecurity原理(五)——扩展与配置

      我们前面已经基本介绍了Spring Security中最重要和常用的组件与功能。

      这篇文章,我们就来了解一下怎样做一些自定义的扩展与配置。

      自定义扩展

      自定义Filter

      自定义Filter应该是最常用的需求了,例如,为了拦截大多数的暴力登录,我们一般会在登录的时候给一个验证码,但是UsernamePasswordAuthenticationFilter没有提供验证码的校验,所以我们就可以自定义一个Filter来处理验证码。

      又如,对于前后端分离项目,我们更多使用Token,而不是Session,也可以通过Filter来处理Token的校验,续期的问题。

      自定义Filter的方式有很多:

      1. 直接实现Filter
      2. 继承GenericFilterBean
      3. 继承OncePerRequestFilter重写doFilterInternal
      4. 继承BasicAuthenticationFilter重写doFilterInternal
      5. 继承AbstractAuthenticationProcessingFilter重写attemptAuthentication
      6. 继承UsernamePasswordAuthenticationFilter重写attemptAuthentication
      7. ……

      后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);
      
      1. addFilter是添加到最后,但并不是最终的最后,因为后面的流程还会添加其他Filter
      2. addFilterAfter,添加在指定Filter之后
      3. addFilterBefore,添加在指定Filter之前
      4. 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

      实现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

      一般配置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

      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());
      

      几个重要的类与接口

      SecurityBuilder

      public interface SecurityBuilder<O> {
      	O build() throws Exception;
      

      就是构建一个特殊对象O的抽象,例如Filter。

      WebSecurity就是一个为了创建Filter对象而设计。

      HttpSecurity也是一个SecurityBuilder,不过它是为了创建DefaultSecurityFilterChain。

      SecurityConfigurer

      public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
      	void init(B builder) throws Exception;
      	void configure(B builder) throws Exception;
      

      SecurityConfigurer目的主要有两个:

      1. init,初始化builder,例如给一些filter设置参数
      2. configure,配置builder,例如创建添加新的filter

      SecurityConfigurerAdapter

      SecurityConfigurer的适配器,提供了init,configure的空实现,并且添加了一个CompositeObjectPostProcessor后置处理器,主要是用来处理Filter。

      AbstractHttpConfigurer

      最主要添加了一个disable方法,基本上很多Filter的configure类都继承了它。

      WebSecurityConfigurer

      public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends SecurityConfigurer<Filter, T> {
      

      WebSecurityConfigurer主要是实例化了类型参数,告诉大家,我就是配置生产Filter的的SecurityBuilder。

      WebSecurityConfigurerAdapter

      public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
      

      WebSecurityConfigurerAdapter更进一步告诉大家,我的SecurityBuilder就是WebSecurity,生产的就是Filter。

      当然WebSecurityConfigurerAdapter没有开空头支票,还提供了很多功能,我们自己要对SpringSecurity进行配置的话,基本都是继承WebSecurityConfigurerAdapter。

      WebSecurity

      WebSecurity是一个SecurityBuilder,所以它的主要职责之一就是创建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

      前面已经分析了,HttpSecurity的目的就一个创建DefaultSecurityFilterChain,注意它的performBuild方法。

      文章目录系列目录前言具体内容去除JWT前端提示信息遇到的问题一、Springsecurity中的UsernameNotFoundException异常无法被正常捕获二、无法统一处理filter中抛出的异常后叙 Spring Security 默认的过滤器链 官网位置:http://docs.spring.io/spring-security/site/docs/5.0.0.M1/reference/htmlsingle/#ns-custom-filters 本文解决问题将自定义的 Filter 加入到 Spring Security 中的 Filter 链中的指定位置。Spring Security 默认的过滤器链官网位置:http://docs.spring.io/spring-security/site/docs/5.0.0.M1/reference/htmlsingle/#ns-custom-filters别名类名称Namespace Elem... 三种配置方法 configure(WebSecurity) 通过重载,配置Spring Security的Filter链 configure(HttpSecurity) 通过重载,配置如何通过拦截器保护请求,用来拦截请求,配置白名单和过滤 configure(AuthenticationManagerBuilder) 通过重载,配置user-detail服务,身份认证接口调用 用户存储方式 auth.inMemoryAuthentication() .withUser(“admin”).passw HttpSecurity类中定义了初始化过滤器的集合 public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity> implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> 这个需求应该扩展spring security。主要扩展以下类: UsernamePasswordAuthenticationFilter类的attemptAuthentication方法,主要获取request传递的参数; UsernamePasswordAuthenticationToken类,主要用于增加认证所用到的额外参数; DaoAuthenticationProvider类的retrieveUser方法,主要把UsernamePasswordAuthenticationToken扩展类的额外参数 httpSecurity.antMatcher("/xxx/**").addFilterBefore(xxApiFilter, UsernamePasswordAuthenticationFilter.class); @Component public class XXApiFilter extends OncePerRequestFilter { @Override protected DefaultSecurityFilterChain performBuild() { filters.sort(comparator); return new Defau
 
推荐文章
慷慨的铁板烧  ·  Java-Stream流和Lambda表达式
1 年前
仗义的铅笔  ·  关于Velocity模板引擎的一些思考总结 - 知乎
2 年前
乐观的竹笋  ·  C# Excel操作 - 一杯水M - 博客园
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号