Spring Security Principle (5) - Extension and Configuration

Introduction

Spring Security Principle (1) - Preliminary Exploration of Spring Security Principle (2) - Authentication Spring Security Principle (3) - Authorization Spring Security Principle (4) - Filter Spring Security Principle (5) - Extension and Configuration

We have basically introduced the most important and commonly used components and functions in Spring Security.

In this article, let's take a look at how to do some custom extensions and configurations.

Important process of Spring Security

custom extension

Custom Filter

Custom Filter should be the most commonly used requirement. For example, in order to intercept most brute force logins, we usually give a verification code when logging in, but UsernamePasswordAuthenticationFilter does not provide verification code verification, so we can customize one Filter to handle verification codes.

For another example, for front-end and back-end separation projects, we use Token more instead of Session, and we can also use Filter to handle the verification and renewal of Token.

There are many ways to customize Filter:

  1. Implement Filter directly
  2. Inherit GenericFilterBean
  3. Inherit OncePerRequestFilter and rewrite doFilterInternal
  4. Inherit BasicAuthenticationFilter and override doFilterInternal
  5. Inherit AbstractAuthenticationProcessingFilter to override attemptAuthentication
  6. Inherit UsernamePasswordAuthenticationFilter to override attemptAuthentication
  7. ……

The last three are authentication-related Filters.

Because it involves forwarding redefinition and other issues, a request Filter may be called more than once. OncePerRequestFilter is to solve this problem, it guarantees that a request that inherits its Filter will only be called once.

BasicAuthenticationFilter itself inherits OncePerRequestFilter, so you don't need to deal with the multiple calls of Filter caused by forwarding and so on.

AbstractAuthenticationProcessingFilter adds authentication failure, authentication success and other processing, but it does not deal with the problem that a request may be called multiple times.

For form authentication, if you want to be lazy, you can inherit UsernamePasswordAuthenticationFilter yourself. For example, inherit UsernamePasswordAuthenticationFilter, deal with the verification code problem first, and then call the attemptAuthentication method of UsernamePasswordAuthenticationFilter if the verification is successful.

Anyway, the custom Filter is very flexible, choose according to your own preferences.

How to configure the customized Filter?

In the simplest way, a custom configuration class overrides the configure method of WebSecurityConfigurerAdapter:

 @Override
protected void configure(HttpSecurity http) {
    http.addFilter(zzzFilter)
            .addFilterAfter(aaaFilter)
            .addFilterBefore(yyyFilter, UsernamePasswordAuthenticationFilter.class)
            .addFilterAt(xxxFilter,UsernamePasswordAuthenticationFilter.class);
}
  1. addFilter is added to the end, but it is not the end, because other Filters will be added later in the process
  2. addFilterAfter, added after the specified Filter
  3. addFilterBefore, added before the specified Filter
  4. addFilterAt, before adding the specified Filter, will not overwrite and delete the specified Filter, which feels similar to addFilterBefore

Of course, you can also use the SecurityConfigurerAdapter method:

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

Custom logout success handler

Implement the LogoutSuccessHandler interface, and generally return json data, so that the front end can give prompt information.

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

Configuration method:

protected void configure(HttpSecurity http){
    http..logout()
				.logoutSuccessHandler(new JwtLogoutSuccessHandler());
}

Authentication failure handler

Implement the AuthenticationFailureHandler interface, which generally returns json data, and then the front end decides the prompt information and jump based on the returned data.

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

Implement the AuthenticationSuccessHandler interface. If there is logic to generate token, you can put it here.

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

Of course, you can also inherit SimpleUrlAuthenticationSuccessHandler.

The configuration is also the old way:

@Override
protected void configure(HttpSecurity http) throws Exception {

		http.formLogin()
				.successHandler(loginSuccessHandler)
				.failureHandler(loginFailureHandler)
}

Authentication exception jump entry

Implement the AuthenticationEntryPoint interface. This interface is called after the ExceptionTranslationFilter filter intercepts the authentication exception. Generally, it is to jump to the login page. You can refer to: 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();
	}
}

Authorization exception handler

Implements the AccessDeniedHandler interface, which is called after an authorization exception intercepted by 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();

	}
}

Custom Authentication Credentials

Authentication can be implemented, or AbstractAuthenticationToken can be inherited. Generally not needed unless you want to customize the authenticator.


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;
    }
}

custom authenticator

Implement the AuthenticationProvider interface.

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

custom voter

It can implement the AccessDecisionVoter interface, or directly inherit the WebExpressionVoter and the like. Depending on the specific requirements, it is generally not required, unless you need to design a new authorization system yourself.

public class MyExpressionVoter extends WebExpressionVoter {
    @Override
    public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
        return 1 ;//-1反对,0弃权,1同意
    }
}

configure

Configure WebSecurity

Generally, WebSecurity is configured to ignore static resource verification.

@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");
    }
}

The matching rule is used: AntPathRequestMatcher

Configure HttpSecurity

There are too many things that HttpSecurity can configure. The following is an example for reference. Note that many unnecessary configurations are repeated, just to show what can be configured.

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

Several important classes and interfaces

SecurityBuilder

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

It is to construct an abstraction of a special object O, such as Filter.

WebSecurity is designed to create Filter objects.

HttpSecurity is also a SecurityBuilder, but it is used to create the DefaultSecurityFilterChain.

SecurityConfigurer

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

There are two main purposes of SecurityConfigurer:

  1. init, initialize the builder, such as setting parameters for some filters
  2. configure, configure the builder, such as creating and adding new filters

SecurityConfigurerAdapter

The adapter of SecurityConfigurer provides an empty implementation of init and configure, and adds a CompositeObjectPostProcessor post-processor, which is mainly used to process Filter.

AbstractHttpConfigurer

The most important thing is to add a disable method, which is basically inherited by many filter configure classes.

WebSecurityConfigurer

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

}

WebSecurityConfigurer mainly instantiates type parameters, and tells you that I am the SecurityBuilder that configures the production Filter.

WebSecurityConfigurerAdapter

public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
}

WebSecurityConfigurerAdapter goes a step further and tells you that my SecurityBuilder is WebSecurity, and what it produces is Filter.

Of course, WebSecurityConfigurerAdapter does not make empty promises, and also provides many functions. If we want to configure SpringSecurity ourselves, we basically inherit WebSecurityConfigurerAdapter.

WebSecurity

WebSecurity is a SecurityBuilder<Filter>, so one of its main responsibilities is to create a Filter, focusing on its build method, which inherits the build of AbstractSecurityBuilder. The specific logic is in the doBuild method of AbstractConfiguredSecurityBuilder.

@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;
    }
}

Very standard template method pattern.

The logic that actually executes the build is in the performBuild method, which creates a FilterChainProxy in WebSecurity and a DefaultSecurityFilterChain in HttpSecurity.

HttpSecurity

As previously analyzed, the purpose of HttpSecurity is to create a DefaultSecurityFilterChain, pay attention to its performBuild method.

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324036993&siteId=291194637