Description
*Keeping Original Post for Context
Expected Behavior
The Authentication Interface is core to Spring Security, however there are some genuine pain points with it and the Developer Experience with Spring Security. This suggestion should be seen in line with the efforts of #13266 .
To help improve this I suggest adding a single Generic to Authentication
,
public interface Authentication<T> extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
// Alternative would be keep Object getPrincipal() and include a new Function
T getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated);
}
// New Interface
public interface UserAuthentication extends Authentication<UserDetails> {
@Override
default Collection<? extends GrantedAuthority> getAuthorities() {
return this.getPrincipal().getAuthorities();
}
}
// Example of a Future Interface
public interface JwtAuthentication extends Authentication<ImaginarySpringSecurityJwt> {}
This is similar to what was mentioned here, however taking a more step based approach, and focusing first on the main downstream usage.
To reduce adoption friction I'd also expect the following interface to be created,
public interface LegacyAuthentication extends Authentication<Object> {}
Current Behavior
Weak Typing with
Authentication.getPrinciple() -> Object
Context
The Authentication
Interface is very old, and thus it falls into a hard spot of needing improvements, but being so core to the framework that adjusting it will have serious considerations for everyone involved.
Personally at work I've had to deal with the pain I assume many people have, which is Object getDetails()
returns a UserDetails
Object, but getPrincipal()
doesn't, as getDetails
sounds like it should return UserDetails
. This means something like @AuthenticationPrincipal
isn't useful, most of the Testing Support provided by Spring Security requires custom workarounds to use, etc. By putting a Generic around getPrincipal()
and/or introducing a UserAuthentication
, we're able to hint to Developers the approach Spring Security recommends, which I expect should improve the downstream experience of Spring Security.
As mentioned in the Issue Comment linked above, the ideal world would be Authentication<C, D, P>
, but due to the core functionality of Authentication
, we should take this safely, and I believe we can get most of the benefits for Developers by limiting the Generic to getPrincipal()
.
Alternatives
Wrap Authentication for typing, as an example
public TypedAuthentication<T> extends Authentication {
@Override
T getPrinciple();
}
This would provide a new Code Path for this "New" style to use, however would still leave the underlying smell. I believe we should only look into it if the potential User Disruption from the Generic Authentication is deemed to high to even consider this.
Downstream Issues
Although not on the Template, I figured if I'm going to suggest a breaking change, I should list the considerations I know about.
1, Contributor Conflicts, sweeping updates across all Modules as they move from Authentication
to LegacyAuthentication
, or Authentication<?>
. This could be a source of merge conflicts, and without additional effort from maintainers down the line negate the Type Support this Issue is looking to provide.
2, Large Scale Documentation Updates, as mentioned Authentication
is core to Spring Security, so this could entail reviewing the entire Spring Security Documentation for adjustments.
3, User Adoption Friction, given how we're swapping for a Generic our Users will have to deal with adjusting their implementations or variable references in line with the adjustment, providing LegacyAuthentication
should hopefully improve that experience for them.
Post Attempt
The unfortunate truth is that Authentication
is too ingrained in Spring to attempt to adjust it with Generics. I attempted it this afternoon to see the amount of effort it would take. Several hundred lines and files later I've conceded to the Java Compiler (And the pain of adding <?>
to every reference)
I believe the alternate approach of introducing a new TypedAuthentication<T> extends Authentication
is the best approach now, ideally with an upper bound like UserDetails
to make the Compiler happy. The main issue I was experiencing was that because we had no upper bound, Java would complain about the capture groups, and because the original value was Object
I couldn't introduce a new Upper Bound without creating breaking change that would invalidate the entire effort.