Multiple AuthFactories in Dropwizard - dropwizard

I want to use two different authfactories in dropwizard, Basic and OAuth. Like this :-
BasicAuthFactory<UserA> authFactory = new BasicAuthFactory<UserA>(new IngestionConsoleAuthenticator(),"SUPER SECRET STUFF", UserA.class);
OAuthFactory<UserB> authFactory2 = new OAuthFactory<UserB>(new PSVAppAuthenticator(), "Secret", UserB.class);
environment.jersey().register(AuthFactory.binder(authFactory));
environment.jersey().register(AuthFactory.binder(authFactory2));
The resources with #Auth UserA works fine, but with #Auth UserB are reachable without any authentication. How can i make both work simultaneously?

http://www.dropwizard.io/manual/auth.html#chained-factories
#Override
public void run(ExampleConfiguration configuration,
Environment environment) {
ChainedAuthFactory<User> chainedFactory = new ChainedAuthFactory<>(
new BasicAuthFactory<>(new ExampleBasicAuthenticator(), "SUPER SECRET STUFF", User.class),
new OAuthFactory<>(new ExampleOAuthAuthenticator(), "SUPER SECRET STUFF", User.class));
environment.jersey().register(AuthFactory.binder(chainedFactory));
}
For this to work properly, all chained factories must produce the same
type of principal, here User.

Hint: The dropwizard documentation is good, so please just read the example code there. Example below is from Dropwizard 1.0.0 Documentation
#Override
public void run(ExampleConfiguration configuration,
Environment environment) {
AuthFilter basicCredentialAuthFilter = new BasicCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleBasicAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Basic")
.buildAuthFilter();
AuthFilter oauthCredentialAuthFilter = new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(new ExampleOAuthAuthenticator())
.setAuthorizer(new ExampleAuthorizer())
.setPrefix("Bearer")
.buildAuthFilter();
List<AuthFilter> filters = Lists.newArrayList(basicCredentialAuthFilter, oauthCredentialAuthFilter);
environment.jersey().register(new AuthDynamicFeature(new ChainedAuthFilter(filters)));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder<>(User.class));
}

Related

Single resource server with multiple authorisation servers, one for each tenant

I am working on a Spring Boot application, which is basically a resource server. As of now, my application has one tenant, which gets authenticated with an authorization server, external to my application.
In order to achieve the same, as of now, I have made the following changes in my application:
config changes are as following:
spring.security.oauth2.client.registration.tenant1.client-id=abcd
spring.security.oauth2.client.registration.tenant1.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant1.authorization-grant-type=authorization_code
myapp.oauth2.path=https://external.authorization.server/services/oauth2/
spring.security.oauth2.client.provider.tenant1.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant1.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant1.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant1.user-name-attribute=name
As of now, I am fetching client secrets from Vault, so I had to define the OAuth2 configuration as follows:
#EnableConfigurationProperties(OAuth2ClientProperties.class)
#Conditional(ClientsConfiguredCondition.class)
#Configuration
public class OAuth2Configuration {
static final String OAUTH2_CLIENT_SECRET_KEY = "oauth2_client_secret";
private static final Logger log = LoggerFactory.getLogger(OAuth2Configuration.class);
private static final String OAUTH2_REGISTRATION_MISSING =
"oAuth2 registration properties are missing";
private final ApplicationSecretProvider applicationSecretProvider;
private final Map<String, ClientAuthenticationMethod> clientAuthenticationMethodMap =
new HashMap<>();
private final String authenticationMethod;
public OAuth2Configuration(
#Value("${spring.security.oauth2.client.registration.tenant1.client-authentication-method}")
final String authenticationMethod,
final ApplicationSecretProvider applicationSecretProvider) {
this.authenticationMethod = authenticationMethod;
this.applicationSecretProvider = applicationSecretProvider;
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.POST.getValue(), ClientAuthenticationMethod.POST);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.BASIC.getValue(), ClientAuthenticationMethod.BASIC);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.NONE.getValue(), ClientAuthenticationMethod.NONE);
}
#Bean
public InMemoryClientRegistrationRepository getClientRegistrationRepository(
OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
//We will have only one client registered for oAuth
if (CollectionUtils.isEmpty(registrations)) {
log.error(OAUTH2_REGISTRATION_MISSING);
throw new IllegalStateException(OAUTH2_REGISTRATION_MISSING);
}
ClientRegistration registration = registrations.get(0);
ClientRegistration.Builder builder = ClientRegistration.withClientRegistration(registration);
ClientAuthenticationMethod clientAuthenticationMethod =
getClientAuthenticationMethod(authenticationMethod);
ClientRegistration completeRegistration = builder
.clientSecret(applicationSecretProvider.getSecretForKey(OAUTH2_CLIENT_SECRET_KEY))
.clientAuthenticationMethod(clientAuthenticationMethod)
.build();
return new InMemoryClientRegistrationRepository(completeRegistration);
}
protected ClientAuthenticationMethod getClientAuthenticationMethod(String grantType) {
ClientAuthenticationMethod retValue = clientAuthenticationMethodMap.get(grantType);
if (retValue == null) {
return ClientAuthenticationMethod.NONE;
}
return retValue;
}
}
Then I extended DefaultOAuth2UserService in order to save user details in my application as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
private AuthorityRepository authRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Autowired
public void setAuthorityRepository(AuthorityRepository
authorityRepository) {
this.authorityRepository = authorityRepository;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
DefaultOAuth2User oAuth2User = (DefaultOAuth2User) super.loadUser(userRequest);
Collection<GrantedAuthority> authorities = new HashSet<>(oAuth2User.getAuthorities());
Map<String, Object> attributes = oAuth2User.getAttributes();
...
return new DefaultOAuth2User(authorities, oAuth2User.getAttributes(), userNameAttributeName);
}
}
Security configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customoAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customoAuth2UserService) {
this.customoAuth2UserService = customoAuth2UserService;
}
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()
//.and().oauth2ResourceServer().jwt()
.and()
//.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(customoAuth2UserService);
http.cors().disable();
}
}
Now, I would like to onboard multiple tenants using OAuth2 as well. Say I want to onboard another tenant tenant2. In order to achieve this, I think, I need to do the following changes in the existing code base as follows:
adding config entries in the properties file as above:
spring.security.oauth2.client.registration.tenant2.client-id=efgh
spring.security.oauth2.client.registration.tenant2.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.tenant2.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant2.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant2.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant2.user-name-attribute=name
I need to do changes in the security configuration class:
SecurityConfiguration and OAuth2 configuration class OAuth2Configuration as well. But I am not able to understand what should I add there in order to make my applications work seamlessly for multiple tenants.
In this context, I found this related post: Dynamically register OIDC client with Spring Security OAuth in a multi-tenant stup, but could not get any concrete idea regarding what changes should I do in the existing code base to make my application work in multi-tenancy set up.
Could anyone please help here?
I think there's a bit of confusion that it might help to clear up.
First, it seems that you are not actually building a resource server, as a resource server would require an access token for authentication. Using .oauth2Login() is for either OAuth 2.0 or OpenID Connect 1.0 login, which is a regular application in most respects except how you log in. You still have a browser session after login is successful, which you would not have in a resource server.
Second, configuring a static number of client registrations isn't really quite the same as building a multi-tenant application. Perhaps you're building up to that later, by demonstrating two clients. When configuring two clients using static configuration properties, nothing is really different from a single configuration, other than that there are two possible registrationIds.
Start by building a simple hello world application, such as the OAuth 2.0 Login Sample. If you add a second client registration to your properties, you'll notice that the auto-generated login page (/login) simply shows two links, one for each client. See docs for more on this.
The default URI for initiating the authorization_code flow is /oauth2/authorization/{registrationId}, which means navigating to /oauth2/authorization/abcd launches the first client's login flow. Navigating to /oauth2/authorization/efgh launches the second client's login flow. There's not really anything else needed to support multiple login clients other than understanding how to initiate login.
If you wish to support a fully multi-tenant login configuration, you would then provide a custom ClientRegistrationRepository, which you have done. The only difference is that you should no longer seek to configure clients through the Spring Boot properties, as that seems to be the point that is confusing in your example. If you want to use properties for some of the configuration, create your own configuration properties for your custom repository implementation. Typically at that point, all of this configuration would come from a database.
I would start with that progression (hello world, two statically configured clients, custom ClientRegistrationRepository) then proceed to add other custom components. It will help illustrate the differences at each point.

Dynamically register OIDC client with Spring Security OAuth in a multi-tenant stup

I am building a multi-tenant application which should enable each tenant to use its own IAM provider for the purpose of users authentication. So finally, for example, Tenant1 can use Keycloak, Tenant2 can use OneIdentity, Tenant3 any IAM provider of its choice... The application should enable registering a new tenant together with its IAM provider dynamically (at runtime). I register the OIDC clients having a ClientRegistrationRepository, like:
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(
ClientRegistrations.fromIssuerLocation("iam-provider-1-issuer-location")
.registrationId("registration-id")
.clientId("client-id")
.clientSecret("client-secret")
.build(),
ClientRegistrations.fromIssuerLocation("iam-provider-2-issuer-location")
.registrationId("registration-id")
.clientId("client-id")
.clientSecret("client-secret")
.build(),
);
}
But, the ClientRegistrationRepository doesn't provide a way to update the client registrations. Is there a way to introduce a new client registration at runtime which will be taken into consideration from Spring Security?
Redirect to /oauth2/authorization/{registrationId} in the login controller.
#GetMapping(path = "/login")
public String defaultLogin(HttpServletRequest request) {
String domainName = request.getServerName();
Tenant tenant = tenantService.findByDomainName(domainName);
return String.format("redirect:/oauth2/authorization/%s", tenant.getId());
}
And override findByRegistrationId method, and you can update ClientRegistration as you want.
#Override
public ClientRegistration findByRegistrationId(String registrationId) {
Tenant tenant = tenantService.findById(registrationId);
return ClientRegistration.withRegistrationId(registrationId)
.clientId(tenant.getClientId())
.clientSecret(tenant.getClientSecret())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email")
.clientName(registrationId)
.authorizationUri(String.format("https://%s/authorize", tenant.getAuthProviderDomain()))
.tokenUri(String.format("https://%s/oauth/token", tenant.getAuthProviderDomain()))
.userInfoUri(String.format("https://%s/userinfo", tenant.getAuthProviderDomain()))
.jwkSetUri(String.format("https://%s/.well-known/jwks.json", tenant.getAuthProviderDomain()))
.userNameAttributeName("name")
.build();
}

How override the default BCryptPasswordEncoder created through PasswordEncoderFactories?

I know that in Spring Security would arise the following:
There was an unexpected error (type=Internal Server Error, status=500).
There is no PasswordEncoder mapped for the id "null"
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
And the solution is define a PasswordEncoder. For simplicity is possible define the following:
#Bean
PasswordEncoder encoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
Now, behind the scene the createDelegatingPasswordEncoder() method is defined how (it so far until currently for Spring Security 5.4.2) (See the PasswordEncoderFactories class for more details):
#SuppressWarnings("deprecation")
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256",
new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
Now about the BCryptPasswordEncoder class, it works with some defaults such as:
version: $2a
strength: 10
What happens if declare the following:
#Bean
PasswordEncoder bcryptEncoder() {
return new BCryptPasswordEncoder(BCryptVersion.$2Y, 12);
}
How I can add or override the default BCryptPasswordEncoder created with the custom BCryptPasswordEncoder into the default settings? I want keep all the other defaults
Note: In the PasswordEncoderFactories class (for the createDelegatingPasswordEncoder method), the DelegatingPasswordEncoder class is used behind the scenes. That class does not offer an approach to override too.
If you are creating a new application, you very likely do not need DelegatingPasswordEncoder and instead should use an adaptive one-way function password encoder, for example BCryptPasswordEncoder.
To do this, you can expose a BCryptPasswordEncoder as a bean.
#Bean
PasswordEncoder bcryptEncoder() {
return new BCryptPasswordEncoder(BCryptVersion.$2Y, 12);
}
Then, when a user registers, you can encode their password using the BCryptPasswordEncoder before saving it to your data store. For example:
UserDetails userDetails = User
.withUsername(username)
.password(bcryptEncoder.encode(password))
.roles("USER")
.build();
If you are migrating an existing application, that is where DelegatingPasswordEncoder is useful.
The DelegatingPasswordEncoder allows for validating passwords in multiple formats. It uses the prefixed ID (e.g {bcrypt}) to look up which PasswordEncoder should be used.
Consider a legacy application that uses plaintext passwords.
To migrate the application, you would prefix the plaintext passwords with {noop} and use a DelegatingPasswordEncoder like the one below:
#Bean
public PasswordEncoder delegatingPasswordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("bcrypt", new BCryptPasswordEncoder(BCryptVersion.$2Y, 12));
encoders.put("noop", NoOpPasswordEncoder.getInstance());
return new DelegatingPasswordEncoder("bcrypt", encoders);
}
This DelegatingPasswordEncoder will encode and validate any newly created password using the BCryptPasswordEncoder, while still being able to validate the legacy plaintext passwords using the NoOpPasswordEncoder.
Spring Security provides the PasswordEncoderFactories.createDelegatingPasswordEncoder() method as a convenient default, but it is unlikely that your application uses that many different password encodings.
What is more likely, is that your application uses 2 different encodings, the legacy one (e.g noop) and the modern one (e.g bcrypt), in which case you can use a PasswordEncoder similar to the delegatingPasswordEncoder described above.
The point of these examples is to say that in most situations you do not need the defaults that are set in createDelegatingPasswordEncoder. However, if you still want to use the encoders from createDelegatingPasswordEncoder, except for bcrypt, you can use a PasswordEncoder that looks like this:
#Bean
public PasswordEncoder delegatingPasswordEncoder() {
Map<String, PasswordEncoder> encoders = new HashMap<>();
// Use this encoder for bcrypt
encoders.put("bcrypt", new BCryptPasswordEncoder(BCryptVersion.$2Y, 12));
DelegatingPasswordEncoder delegatingPasswordEncoder =
new DelegatingPasswordEncoder("bcrypt", encoders);
PasswordEncoder defaultDelegatingPasswordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
// If a password ID does not match "bcrypt", use defaultDelegatingPasswordEncoder
delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(defaultDelegatingPasswordEncoder);
return delegatingPasswordEncoder;
}

Swagger 2.0 where to declare Basic Auth Schema

How do I define basic authentication using Swagger 2.0 annotations and have it display in swagger UI.
In the resource I have:
#ApiOperation(value = "Return list of categories", response=Category.class, responseContainer="List", httpMethod="GET", authorizations = {#Authorization(value="basicAuth")})
public Response getCategories();
I looked here:
https://github.com/swagger-api/swagger-core/wiki/Annotations#authorization-authorizationscope
And it says "Once you've declared and configured which authorization schemes you support in your API, you can use these annotation to note which authorization scheme is required on a resource or a specific operation" But I can't find anything that talks about where to declare and configure the authorization schemes.
Update:
I found code on how to declare the schema, but I still do not see any information about the authentication schema in the UI. I'm not sure what I am missing
#SwaggerDefinition
public class MyApiDefinition implements ReaderListener {
public static final String BASIC_AUTH_SCHEME = "basicAuth";
#Override
public void beforeScan(Reader reader, Swagger swagger) {
}
#Override
public void afterScan(Reader reader, Swagger swagger) {
BasicAuthDefinition basicAuthDefinition = new BasicAuthDefinition();
swagger.addSecurityDefinition(BASIC_AUTH_SCHEME, basicAuthDefinition);
}
}
Using Springfox 2.6 annotations, you must first define Basic authentication as one of the security schemes when you set up the Docket in your configuration, like this:
List<SecurityScheme> schemeList = new ArrayList<>();
schemeList.add(new BasicAuth("basicAuth"));
return new
Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo)
.securitySchemes(schemeList)
...
Then you can use the Springfox annotations in your service to set Basic Auth for the operation for which you want to require authentication:
#ApiOperation(value = "Return list of categories", response=Category.class, responseContainer="List", httpMethod="GET", authorizations = {#Authorization(value="basicAuth")})
public Response getCategories();
I struggeled with this as well. In my case i used the swagger-maven-plugin. To solve this i added this within the maven plugin:
<securityDefinitions>
<securityDefinition>
<name>basicAuth</name>
<type>basic</type>
</securityDefinition>
</securityDefinitions>
After that i was able to add it on my resource like this:
#Api(value = "My REST Interface", authorizations = {#Authorization(value="basicAuth")})
The generated json included the security element for each endpoint:
"security":[{
"basicAuth" : []
}]
And the security definition:
"securityDefinitions" : {
"basicAuth" : {
"type" : "basic"
}
}
I hope this helps others as well.
You can use the #SwaggerDefinition
http://swagger.io/customizing-your-auto-generated-swagger-definitions-in-1-5-x/
or you can configure the swagger object directly, here's an example
http://www.programcreek.com/java-api-examples/index.php?source_dir=rakam-master/rakam/src/main/java/org/rakam/WebServiceRecipe.java

Spring Security - Preauthentication - Authorization and Set Roles Defined in DB

The authentication for our application happens through siteminder agent but the authorization is controlled through our application.
I am using org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter to check the header. I have also defined UserDetailsService to load the user details.
I also need to set the role for the user so that I can use spring security tag libraries and other spring methods to check the role and show the options.
How do I implement this?
I have tried the below statements in my user details service implementation, but doesn't seem to work.
Authentication auth = new UsernamePasswordAuthenticationToken(user, null, roles);
SecurityContextHolder.getContext().setAuthentication(auth);
I have also read about AbstractPreAuthenticatedProcessingFilter class but looks like this is may not be useful for this purpose.
Any help on this issue will be very helpful.
Thanks!
I was trying to set the roles(using the statements in my question) in the UserDetailsService
implementation and it was not working.
Solution-1:
I have written a sub class PreAuthenticatedAuthenticationProvider and overridden the authenticate method as below :
public class CustomPreAuthenticatedAuthenticationProvider extends PreAuthenticatedAuthenticationProvider {
#Autowired
DBConfig dbConfig;
#Override
public Authentication authenticate(Authentication authentication)throws AuthenticationException {
Authentication auth = super.authenticate(authentication);
User user = (User)auth.getPrincipal();
Set<Role> roles = new HashSet<Role>();
String[] rolesArray = dbConfig.getRoles(user.getAccessLevel());
for(String role: rolesArray){
Role r = new Role();
r.setName(role);
roles.add(r);
}
user.setRoles(roles);
auth = new UsernamePasswordAuthenticationToken(user, null, roles);
return auth;
}
}
Solution-2 :
I tried setting the roles in the controller (the home page after authentication) and it worked. But looks like Solution-1 is a standard solution.

Resources