spring-projects / spring-projects/spring-security

Simplify MVC-based authentication

Open
#12,985 7 comments 10 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

in: web type: enhancement
Dominant language
Java
Stars
9.6k
Forks
6.3k
Avg merge
2d 11h
Merged PRs (30d)
52

Description

It's common in an application to use Spring MVC to publish a custom login page, for example like so:

@GetMapping("/login")
String startLogin() {
    return "login";
}

And it's quite reasonable that the same application may want to do the same with customizing the subsequent POST:

@PostMapping("/login")
String finishLogin(@RequestParam("username") String username, @RequestParam("password") String password) {
    // ...
}

In this case, the application needs to remember to do several things, like store the context in SecurityContextHolderStrategy and SecurityContextRepository and invoke the set of SessionAuthenticationStrategys.

Often, the easiest thing is for the application to configure a custom filter that extends AbstractAuthenticationProcessingFilter instead. This can lead to some disjointedness in the application since, in this brief example, the GET is in Spring MVC, but the POST is in the SecurityFilterChain. It also forces the application down a path of using Spring-Security-specific components for things that Spring MVC was built for.

It would be nice if those using Spring MVC for processing logins didn't have to remember so much boilerplate. In addition to making it simpler, this will make it more secure as well for circumstances like session fixation protection.

One way to do this would be to introduce addtional method argument resolvers, like so:

@PostMapping("/login")
@FormLogin
String finishLogin(Authentication authentication, AuthenticationResultProcessor processor) {
    Authentication result = performLogin(authentication);
    processor.process(result);
    return "redirect:/home";
}

The @FormLogin annotation would exercise Spring Security components to read the request and formulate the correct Authentication instance. The AuthenticationResultProcessor would be prepared to perform the needed Spring Security actions to complete authentication, like storing the SecurityContextRepository.

This approach lends itself nicely to allowing the application to use Spring MVC primitives instead of needing to learn and understand filters, authentication success/failure handlers, and the like.

The pattern could be repeated for other authentication mechanisms, as in @OAuth2Login and @Saml2Login. The AuthenticationResultProcessor instances that are prepared could be different for each one. Consider the following:

@Autowired
OpenSaml4AuthenticationProvider provider;

@PostMapping("/saml2/login")
@Saml2Login
String finishLogin(Authentication authentication, AuthenticationResultProcessor processor) {
    Saml2Authentication result = this.provider.authenticate(authentication);
    Authentication custom = doCustomWork(result);
    processor.process(custom);
    return "redirect:/home";
}

In this case, the argument resolver uses the correct AuthenticationConverter to prepare a Saml2AuthenticationToken that includes the <saml2:Response>, the RelyingPartyRegistration, etc. And the response updates the security context repository, holder, and runs any session authentication strategies.

If the controller method throws an exception, then the ExceptionTranslationFilter will take effect. Or, if that needs to be customized as well, then the application could use a Spring MVC @ExceptionHandler instead. As this is already simple, no work is proposed here to simplify that.

This ticket proposes the following work:

  • Introduce the AuthenticationResultProcessor interface with a #process(Authentication) method
  • Introduce argument resolvers that coordinate with AuthenticationConverters to produce an Authentication instance corresponding to the appropriate annotation
  • Introduce argument resolvers that produce an AuthenticationResultProcessor instance corresponding to the appropriate annotation. It would mitigate session fixation, update the security context repository, and the other Spring Security boilerplate. Since it relies on Spring MVC for the rest, it would not perform any redirection or forwarding

Note that this proposal does not reuse AuthenticationSuccessHandler as that has at least two drawbacks. The first is that it makes the @Controller method signature more complex by requiring the HttpServletRequest and HttpServletResponse objects. The second is that implementations would fundamentally differ in their relationship to existing implementations to the point of mutual exclusion. For example, existing AuthenticationSuccessHandlers primarily redirect and forward, but these new ones would do everything but that. Existing AuthenticationSuccessHandlers do not manage the SecurityContextRepository, SecurityContextHolderStrategy, or SessionAuthenticationStrategy set, but these new ones would.

Future optimizations could possibly include having @Controller methods return an Authentication instance for processing. In that case, the result handler would invoke the AuthenticationResultProcessor as well as any globally configured AuthenticationSuccess/FailureHandler, for example:

@PostMapping("/login")
@FormLogin
Authentication finishLogin(Authentication authentication) {
    return performLogin(authentication);
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the proposed Spring MVC controller flow and the existing AuthenticationConverter, SecurityContextRepository, SecurityContextHolderStrategy, and SessionAuthenticationStrategy responsibilities described in the issue. Define the AuthenticationResultProcessor and annotation-based argument resolvers, then verify that form, OAuth2, and SAML2 login inputs produce the right Authentication and that processing updates security state without redirecting or forwarding.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring
Domain
authentication
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.