Summary
The authentication reading/authentication request creation is not reuseable, because it's embedded in web related classes.
Description
(Lately) there is a rich influx of new technologies that mirror similar behavior as web does (having headers along with the actual data). Examples for such technologies are gRPC and many message brokers such as kafka. These are totally independent of web, but would still benefit from the features offered by spring-security such as BasicAuth, Certificate Authentication, OAuth2... However due to the strict usage of the servlet API it is impossible to reuse most of the code; the issue becomes even more difficult to overcome due to auth extraction, auth validation and response generation happening in the same class (everything except for the actual authentication). And even if we just replicate the behavior for these servlet based classes, we still get a very huge chunk of web related dependencies, that pollute the classpath and increase application size.
Examples
- The BasicAuth "Base64 ->UsernamePasswordToken" transformation code is locked in the web based BasicAuthenticationFilter
- The same applies to DigestAuth/BearerAuth (and maybe all other header based authentications)
- The X509 (certificate) authentication related classes are all located in the spring-security-web package.
As you can see in this library, i had to duplicate a lot of stuff for reading the authentication details. The actual authentication+authorization process could be reused very easily/almost completely.
Originally posted in: https://github.com/spring-projects/spring-security/issues/6381
Comment From: rwinch
Can you propose something more concrete? As I see it we need to have controller code in either case that translates between the APIs.
Comment From: ST-DDT
Yes, I'll try specify my proposal a bit.
Add the following to spring-security-core or a new module (common-auth-schemes) without references to web, but will be used in spring-security-web:
public final class BasicAuthUtil {
public static final String BASIC_AUTH_PREFIX = "Basic ";
private static final String BASIC_AUTH_PREFIX_LOWER = BASIC_AUTH_PREFIX .toLowercase();
public static boolean isBasicAuth(String header) {
return header.toLowercase().startsWith(BASIC_AUTH_PREFIX_LOWER );
}
public static UsernamePasswordAuthenticationToken from(String header) {
if (!isBasicAuth(header)) {
throw new IllegalArgumentException("Not a basic auth header");
}
return fromToken(header.substring(6));
}
// Mainly this method
public static UsernamePasswordAuthenticationToken fromToken(String token) {
byte[] base64Token = token.getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException e) {
throw new BadCredentialsException(
"Failed to decode basic authentication token");
}
String token = new String(decoded, charset);
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
}
return new UsernamePasswordAuthenticationToken(token.substring(0, delim), token.substring(delim + 1));
}
}
And do the same for other token based authentication schemes such as DigestAuth (maybe even for Bearer Tokens , but then without interpreting the Token: new BearerAuthenticationToken(String token)
And move the (X509) certificate authentication classes to core/that module.
Comment From: rwinch
It appears you are looking for some utility methods. The example you provide appears to be looking for HTTP basic authentication utility methods. However, I'm not really convinced that a non-web based application is going to be using HTTP Basic authentication. It is likely to deliver credentials another way entirely. Do you have a sample application that you are working with or that you could point me to?
Comment From: ST-DDT
It appears you are looking for some utility methods. The example you provide appears to be looking for HTTP basic authentication utility methods.
And "access" to the authentication classes, so I don't have to reimplement everything. Currently most authentication schemes are implemented in spring-security-web and thus forces me to add it as a dependency. But if I do that, then spring-boot will try to actually "setup" it but will fail because there is no web endpoint (at least from the spring perspective).
However, I'm not really convinced that a non-web based application is going to be using HTTP Basic authentication. It is likely to deliver credentials another way entirely.
gRPC is very close to actual web requests. They even use HTTP/2. But, they are usually encode the content in protobuf, use code generation for both the server and the client and drop most web/http headers. I don't know how to describe it directly, but IMO gRPC is a mix of (streamed) request and (streamed) responses, websockets, connection pool, with code generation. Or you could call it faster web or the web, if it was made for rest calls/APIs. There is even nginx support for gRPC already.
| web | grpc |
|---|---|
| Tomcat | Netty |
| HttpServletFilter | GrpcInterceptor |
BasicAuth is just the simplest to setup/demonstrate and you can easily send the required headers with grpcurl. Currently I'm at a point where I would like to add support for more authentication options, but it's already there in spring-security-web. It is just NOT compatible/reuseable.
| Part | implemented by spring | self-implemented |
|---|---|---|
| Extracting the Authentication | ❌ (not reusable/not even partially) | ✔️ |
| Authentication providers | ✔️ | ❌ (code-duplication due to incompatible Authentication objects) |
(Security is complex, so I would like to reuse as much code as possible to avoid bugs in those critical components)
AFAICT #6381 also points in that direction. In that case an existing oauth server for web is reused for gRPC. (I haven't checked whether that is just a matter of excluding the web dependency or actually a matter of oauth code runtime dependency to the web library.)
Do you have a sample application that you are working with or that you could point me to?
- Example-readme
- grpc-spring-security-server-example (SecurityConfiguration/Basic-Auth)
- grpc-spring-security-server-example (SecurityConfiguration)
Comment From: rwinch
So you are trying to support gRPC?
Comment From: ST-DDT
So you are trying to support gRPC?
I already implemented the security part for grpc,
I just would like to re-use the "internet-tested" implementations used by spring. Mostly for the implementations of these "AuthenticationReader"s, Authentications and AuthenticationProviders:
-
https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.java#L67-L85 (code not reusable)
-
https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthentication.java (only available inside spring-web)
- https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthenticationProvider.java (only available inside spring-web)
Comment From: fire-papaya
Any updates for this? I am working with third-party api, that uses its own protocol for communication and currently face the same issue
Comment From: rwinch
I'm closing this as declined. If we get a more concrete example of what we need to support, then we can revisit it.