spring boot,

中文版

Advanced Spring Security Usage

qihaiyan qihaiyan Follow Jan 27, 2025 · 5 mins read

Advanced usage of Spring Security, including a loginUrl carrying custom parameters, custom authentication logic, and custom authorization logic.

The complete code is available in the example project https://github.com/qihaiyan/springcamp/tree/main/spring-advanced-security

A complete standalone example project is available at https://github.com/qihaiyan/ng-boot-oauth, with three frontend login implementations in vue/react/angular

login

1. Overview

In real-world project development, Spring Security’s default authentication and authorization logic cannot handle high business complexity. In such cases we need to customize these, including a loginUrl that carries parameters, custom authentication logic, and custom authorization logic.

2. Custom loginUrl with Parameters

When redirecting to the login page, Spring Security lets you specify the login url, but it cannot carry dynamic parameters in the url — for example redirecting to login?param=foo, where foo needs to change dynamically based on specific conditions. To achieve this, we need to specify a custom LoginUrlAuthenticationEntryPoint through exceptionHandling.

public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
    private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    public CustomLoginUrlAuthenticationEntryPoint(String loginFormUrl) {
        super(loginFormUrl);
    }

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
        if (!super.isUseForward()) {
            String redirectUrl = this.buildRedirectUrlToLoginPage(request, response, authException);
            // change login url
            redirectUrl = redirectUrl + "?param=test";
            this.redirectStrategy.sendRedirect(request, response, redirectUrl);
        } else {
            String redirectUrl = null;
            if (super.isForceHttps() && "http".equals(request.getScheme())) {
                redirectUrl = this.buildHttpsRedirectUrlForRequest(request);
            }

            if (redirectUrl != null) {
                this.redirectStrategy.sendRedirect(request, response, redirectUrl);
            } else {
                String loginForm = this.determineUrlToUseForThisRequest(request, response, authException);
                RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
                dispatcher.forward(request, response);
            }
        }
    }
}

The code above defines a custom CustomLoginUrlAuthenticationEntryPoint. In the commence method we can implement our own redirect logic as the business requires, by modifying the redirectUrl.

Reference CustomLoginUrlAuthenticationEntryPoint in the Spring Security configuration through exceptionHandling:

exceptionHandling(customizer ->
                        customizer.authenticationEntryPoint(new CustomLoginUrlAuthenticationEntryPoint("/login")))

3. Custom Authentication Logic

Spring Security’s default authentication logic only checks whether the username and password are valid. To add other validation logic, implement an AuthenticationProvider, then register our own AuthenticationProvider in the authenticationManager.

public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @PostConstruct
    public void init() {
        this.setUserDetailsService(customUserDetailsService);
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) {
        super.additionalAuthenticationChecks(userDetails, authentication);
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String username = userDetails.getUsername();

        // custom authentication check logic
        if (username.equals("need approval")) {
            log.info("invalid request is: {}", req);
            throw new AuthenticationServiceException("Your account is pending approval for access");
        }
    }
}

In the custom CustomAuthenticationProvider above, the custom authentication logic is implemented by overriding the additionalAuthenticationChecks method.

Then specify CustomAuthenticationProvider in the authenticationManager:

@Bean
public AuthenticationManager authenticationManager() {
    return new ProviderManager(customAuthenticationProvider);
}

4. Custom Authorization Logic

Spring Security allows fairly flexible authorization strategies through requestMatchers in the configuration, but it lacks some dynamic capabilities — for example it cannot handle rest-style urls with variables such as /foo/{param}. In this case we can implement a custom AuthorizationManager, then specify it in requestMatchers through the access method.

public class MyRequestAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {

    private final SecurityExpressionHandler<RequestAuthorizationContext> expressionHandler = new DefaultHttpSecurityExpressionHandler();

    public MyRequestAuthorizationManager() {
    }

    // custom authorization check logic
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext context) {
        EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, context);
        String checkParam = Optional.ofNullable(ctx.lookupVariable("param")).map(String::valueOf).orElse(null);

        // the '/public' url doesn't need authentication
        if (checkParam != null && (checkParam.equals("public"))) {
            return new AuthorizationDecision(true);
        }
        return new AuthorizationDecision(!ObjectUtils.isEmpty(authentication.get().getCredentials()));
    }
}

In MyRequestAuthorizationManager, custom authorization is implemented by overriding the check method. For rest-style urls with variables, the variable value can be obtained through the lookupVariable method of EvaluationContext.

Specify the custom AuthorizationManager:

MyRequestAuthorizationManager myRequestAuthorizationManager = new MyRequestAuthorizationManager();
http
    .authorizeHttpRequests(authorize -> authorize
        .requestMatchers("/{param}").access(myRequestAuthorizationManager)
    )
qihaiyan
Written by qihaiyan
业精于勤而荒于嬉,行成于思而毁于随