SpringBoot Security ldap auth against multiple separate AD domains - spring-boot

Is it possible to have SpringBoot use multiple AD authentication providers against different domains?
So, like I have two separate AD controllers
URL: ldap://ad.region1.company.com
baseDN: dc=region1,dc=company,dc=com
and
URL: ldap://ad.region2.company.com
baseDN: dc=region2,dc=company,dc=com
And some code like:
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapdomain, ldapurl);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Where ldapdomain and ldapurl are set to the "region1" values. I also want to be able to authenticate "region2" users. Is there some way to provide both endpoints and have it try both? Or a way to provide a hint in the login on which one to use?

Defile like below mention. Space should be mandatory between two urls.
String ldapdomain = "ldap://ad.region1.company.com ldap://ad.region2.company.com"

Related

Migrating away from Spring Security OAuth 2

I'm having a Spring Boot Auth Microservice. It uses the Oauth2 spring cloud starter dependency which is deprecated nowadays.
buildscript {
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:2.1.9.RELEASE"
}
}
dependencies {
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "org.springframework.boot:spring-boot-starter-data-jpa"
implementation "org.springframework.boot:spring-boot-starter-web"
implementation "org.springframework.cloud:spring-cloud-starter-oauth2:2.1.5.RELEASE"
}
The Schema was taken from here: https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
It also has a custom user_details table. The JPA class is implementing UserDetails. I've also provided an implementation for UserDetailsService which looks up the user in my custom table.
OAuth Configuration is quite forward:
AuthorizationServerConfiguration - where oauth is configured:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableAuthorizationServer
class AuthorizationServerConfiguration : AuthorizationServerConfigurerAdapter() {
#Autowired private lateinit var authenticationManager: AuthenticationManager
#Autowired private lateinit var dataSource: DataSource
#Autowired
#Qualifier("customUserDetailsService")
internal lateinit var userDetailsService: UserDetailsService
#Autowired
private lateinit var passwordEncoder: BCryptPasswordEncoder
override fun configure(endpoints: AuthorizationServerEndpointsConfigurer) {
endpoints
.tokenStore(JdbcTokenStore(dataSource))
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
}
override fun configure(clients: ClientDetailsServiceConfigurer) {
// This one is used in conjunction with oauth_client_details. So like there's one app client and a few backend clients.
clients.jdbc(dataSource)
}
override fun configure(oauthServer: AuthorizationServerSecurityConfigurer) {
oauthServer.passwordEncoder(passwordEncoder)
}
}
WebSecurityConfiguration - needed for class above:
#Configuration
class WebSecurityConfiguration : WebSecurityConfigurerAdapter() {
#Bean // We need this as a Bean. Otherwise the entire OAuth service won't work.
override fun authenticationManagerBean(): AuthenticationManager {
return super.authenticationManagerBean()
}
override fun configure(http: HttpSecurity) {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
}
ResourceServerConfiguration - to configure access for endpoints:
#Configuration
#EnableResourceServer
class ResourceServerConfiguration : ResourceServerConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().cors().disable().csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/token").authenticated()
.antMatchers("/oauth/user/**").authenticated()
.antMatchers("/oauth/custom_end_points/**").hasAuthority("my-authority")
// Deny everything else.
.anyRequest().denyAll()
}
}
These few lines give me a lot.
User Info endpoint (used by microservices)
Client's such as Mobile frontends can authenticate using: POST oauth/token and providing a grant_type=password together with a username and a password.
Servers can authorize using 'oauth/authorize'
Basic Auth support with different authorities is also available as I can fill username + password into the oauth_client_details table:
select client_id, access_token_validity, authorities, authorized_grant_types, refresh_token_validity, scope from oauth_client_details;
client_id | access_token_validity | authorities | authorized_grant_types | refresh_token_validity | scope
-------------------+-----------------------+-------------------------------+-------------------------------------------+------------------------+---------
backend | 864000 | mail,push,app-register | mail,push,client_credentials | 864000 | backend
app | 864000 | grant | client_credentials,password,refresh_token | 0 | app
This is used by the app if there's no oauth token yet.
Other microservices also use this to protect their endpoints - such as in this example:
#Configuration #EnableResourceServer class ResourceServerConfig : ResourceServerConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http.authorizeRequests()
// Coach.
.antMatchers("/api/my-api/**").hasRole("my-role")
.antMatchers("/registration/**").hasAuthority("my-authority")
}
}
Their set up is quite easy:
security.oauth2.client.accessTokenUri=http://localhost:20200/oauth/token
security.oauth2.client.userAuthorizationUri=http://localhost:20200/oauth/authorize
security.oauth2.resource.userInfoUri=http://localhost:20200/oauth/user/me
security.oauth2.client.clientId=coach_client
security.oauth2.client.clientSecret=coach_client
The first three properties just go to my authorization server. The last two properties are the actual username + password that I've also inserted inside the oauth_client_details table. When my microservice wants to talk to another microservice it uses:
val details = ClientCredentialsResourceDetails()
details.clientId = "" // Values from the properties file.
details.clientSecret = "" // Values from the properties file.
details.accessTokenUri = "" // Values from the properties file.
val template = OAuth2RestTemplate(details)
template.exchange(...)
Now my question is - how can I get all of this with the built in Support from Spring Security using Spring Boot? I'd like to migrate away from the deprecated packages and retain all tokens so that users are still logged in afterwards.
We are also running a spring security authorization server and looked into this. Right now there is no replacement for the authorization server component in spring and there does not seem to be a timeline to implement one. Your best option would be to look into an existing auth component like keycloak or nimbus. alternatively there are hosted service like okta or auth0.
Keeping your existing tokens will be a bit of a challange as you would need to import them into your new solution. Our current tokens are opaque while newer auth-solutions tend to use some version of jwt, so depending on your tokens, keeping them may not even be an option.
Right now we consider accepting both old and new tokens for a time until the livetime of our old tokens ends, at wich point we would move fully to the new infrastukture.
So I've ended up developing my own authentication system with a migration API from the old Spring Security OAuth 2 to my system. That way you are not logged out and need to re-login.
I'll describe how I did it in case anyone is interested.
In my scenario it is 2 'microservices'. One being the deprecated auth and the other leveraging it.
Legacy Authentication System
To either get a token as a user you'd send a request to /oauth/token with your username + password.
To refresh a token another request to /oauth/token with your refresh token.
Both cases return your access token + refresh token. You can execute this multiple times per devices and you'd always end up with the same tokens. This is important later.
Tokens are stored as MD5 hashed.
Spring Security OAuth has these tables defined:
oauth_access_token (access tokens)
oauth_approvals (don't know what for, is always empty in my case)
oauth_client_details (contains a basic authorization method when you're not authorized)
oauth_client_token (empty in my case)
oauth_code (empty in my case)
oauth_refresh_token (refresh tokens)
user_details (contains the user data)
user_details_user_role (association between user + roles)
user_role (your roles)
I really didn't use the multi roles functionality, but in any case it's trivial to take that into consideration as well.
New Authentication System
Access token & refresh tokens are uuid4's that I SHA256 into my table.
I can query them easily and check for expiration and throw appropriate HTTP status codes.
I ended up doing a per device (it's just a UUID generated once in the frontend) system. That way I can distinguish when a user has multiple devices (AFAIK, this isn't possible with the old system).
We need these new endpoints
Login with email + password to get an authentication
Migration call from the old tokens to your new ones
Logout call which deletes your authentication
Refresh access token call
Thoughts
I can keep using the user_details table since only my code interacted with it and I expose it via Springs UserDetailsService.
I'll create a new authentication table that has a n:1 relationship to user_details where I store a device id, access token, access token expiry & refresh token per user.
To migrate from the old to the new system, my frontend will send a one time migration request, where I check for the given access token if it's valid and if it is, I generate new tokens in my system.
I'll handle both systems in parallel by distinguishing at the header level Authorization: Bearer ... for the old system & Authorization: Token ... for the new system
Code snippets
I use Kotlin, so in order to have type safety and not accidentally mix up my old / new token I ended up using a sealed inline classes:
sealed interface AccessToken
/** The token from the old mechanism. */
#JvmInline value class BearerAccessToken(val hashed: String) : AccessToken
/** The token from the new mechanism. */
#JvmInline value class TokenAccessToken(val hashed: String) : AccessToken
To get my token from an Authorization header String:
private fun getAccessToken(authorization: String?, language: Language) = when {
authorization?.startsWith("Bearer ") == true -> BearerAccessToken(hashed = hashTokenOld(authorization.removePrefix("Bearer ")))
authorization?.startsWith("Token ") == true -> TokenAccessToken(hashed = hashTokenNew(authorization.removePrefix("Token ")))
else -> throw BackendException(Status.UNAUTHORIZED, language.errorUnauthorized())
}
internal fun hashTokenOld(token: String) = MessageDigest.getInstance("MD5").digest(token.toByteArray(Charsets.UTF_8)).hex()
internal fun hashTokenNew(token: String) = MessageDigest.getInstance("SHA-256").digest(token.toByteArray(Charsets.UTF_8)).hex()
Verifying the tokens with type safety gets pretty easy:
when (accessToken) {
is BearerAccessToken -> validateViaDeprecatedAuthServer(role)
is TokenAccessToken -> {
// Query your table for the given accessToken = accessToken.hashed
// Ensure it's still valid and exists. Otherwise throw appropriate Status Code like Unauthorized.
// From your authentication table you can then also get the user id and work with your current user & return it from this method.
}
}
The validateViaDeprecatedAuthServer is using the old authentication sytem via the Spring APIs and returns the user id:
fun validateViaDeprecatedAuthServer(): String {
val principal = SecurityContextHolder.getContext().authentication as OAuth2Authentication
requireElseUnauthorized(principal.authorities.map { it.authority }.contains("YOUR_ROLE_NAME"))
return (principal.principal as Map<*, *>)["id"] as? String ?: throw IllegalArgumentException("Cant find id in principal")
}
Now we can verify if a given access token from a frontend is valid. The endpoint which generates a new token from the old one is also quite simple:
fun migrateAuthentication(accessToken: AccessToken) when (origin.accessToken(language)) {
is BearerAccessToken -> {
val userId = validateViaDeprecatedAuthServer(role)
// Now, create that new authentication in your new system and return it.
createAuthenticationFor()
}
is TokenAccessToken -> error("You're already migrated")
}
Creating authentication in your new system might look like this:
fun createAuthenticationFor() {
val refreshToken = UUID.randomUUID().toString()
val accessToken = UUID.randomUUID().toString()
// SHA256 both of them and save them into your table.
return refreshToken to accessToken
}
Then you only need some glue for your new 'login' endpoint where you need to check that the email / password matches a given user in your table, create an authentication & return it.
Logout just deletes the given authentication for your user id + device id.
Afterthoughts
I've been using this system now for the last few days and so far it's working nicely. Users are migrating. No one seems to be logged out which is exactly what I've wanted.
One downside is that since the old authentication system didn't distinguish between devices, I have no way of knowing when a user has successfully migrated. He could be using 1 device or 10. I simply don't know. So both systems will need to live side by side for a rather long time and slowly I'll phase out the old system. In which case, I'll force logout you and you need to re-login (and potentially install a new App version if you haven't updated).
Note that the new system is limited to my own needs, which is exactly what I want. I'd prefer it to be simple and maintainable than the Spring Blackbox authentication system.

Invalid JWToken: kid is a required JOSE Header

I am trying to implement an Oauth2 Authorization Server with SpringBoot using this guide as a reference.
My keystore has a single key. I have successfully managed to create a JWToken (I can check it at jwt.io).
I have also a test Resource Server. When I try to access any endpoint I receive the following message:
{
"error": "invalid_token",
"error_description": "Invalid JWT/JWS: kid is a required JOSE Header"
}
The token really does not have a kid header but I can not figure out how to add it. I can only add data to its payload, using a TokenEnchancer. It also seems that I am not the first one with this issue.
Is there any way to add this header or, at least, ignore it at the resource server?
I've been working on an article that might help you out here:
https://www.baeldung.com/spring-security-oauth2-jws-jwk
So, to configure a Spring Security OAuth Authorization Server to add a JWT kid header, you can follow the steps of section 4.9:
create a new class extending the JwtAccessTokenConverter
In the constructor:
configure the parent class using the same approach you've been using
obtain a Signer object using the signing key you're using
override the encode method. The implementation will be the same as the parent one, with the only difference that you’ll also pass the custom headers when creating the String token
public class JwtCustomHeadersAccessTokenConverter extends JwtAccessTokenConverter {
private JsonParser objectMapper = JsonParserFactory.create();
final RsaSigner signer;
public JwtCustomHeadersAccessTokenConverter(KeyPair keyPair) {
super();
super.setKeyPair(keyPair);
this.signer = new RsaSigner((RSAPrivateKey) keyPair.getPrivate());
}
#Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication));
} catch (Exception ex) {
throw new IllegalStateException("Cannot convert access token to JSON", ex);
}
Map<String, String> customHeaders = Collections.singletonMap("kid", "my_kid");
String token = JwtHelper.encode(content, this.signer, this.customHeaders)
.getEncoded();
return token;
}
}
Then, of course, create a bean using this converter:
#Bean
public JwtAccessTokenConverter accessTokenConverter(KeyPair keyPair) {
return new JwtCustomHeadersAccessTokenConverter(keyPair);
}
Here I used a KeyPair instance to obtain the signing key and configure the converter (based on the example of the article), but you might adapt that to your configuration.
In the article I also explain the relevant endpoints provided by the Spring Security OAuth Authentication Server.
Also, regarding #Ortomala Lokni's comment, I wouldn't expect Spring Security OAuth to add any new features at this point. As an alternative, you probably can wait to have a look at Spring Security's Authorization Server features, planned to be released in 5.3.0
I managed to solve it by changing the parameter used to identify the URL where the clients will retrieve the pubkey.
On application.properties, instead of:
security.oauth2.resource.jwk.key-set-uri=http://{auth_server}/.well-known/jwks.json
I used:
security.oauth2.resource.jwt.key-uri=http://{auth_server}/oauth/token_key
If I understood correctly, the key-set-uri config points to an endpoint that presents a set of keys and there is the need for a kid. On the other side key-uri config points to an endpoint with a single key.

How to generate authorization information per API endpoint in Swagger Doc based on Spring-Security annotations

How to reuse Spring-Security annotations like #PreAuthorize or #Secured in Springfox-swagger to avoid maintaining the authorization information twice?
Currently the only options I found to populate security information in the swagger.json is duplicating the role information with swagger-core annotations like
#Secured(ROLE_USER)
#ApiOperation(value = "Get the model", authorizations = {
#Authorization(value = ROLE_USER) })
#GetMapping(value = "model/**")
or specifying globally in the SwaggerConfig::
private SecurityContext apiSecurityContext() {
AuthorizationScope[] authorizationScopes = new AuthorizationScope[] { new AuthorizationScope(Roles.ROLE_USER, "access limited"),
new AuthorizationScope(Roles.ROLE_ADMIN, "access Everything") };
return SecurityContext
.builder()
.securityReferences(newArrayList(new SecurityReference("basic", authorizationScopes)))
.forPaths(PathSelectors.regex("/api.*")) // (PathSelectors.any())
.build();
}
Both ways seems not to be a good solution since I need to maintain the information multiple times or are not precise.
So what do I need to configure in order to let swagger depict the authorization roles a requester needs to access a certain rest resource from the spring-security annotations?

Spring Security + Auth LDAP : BindRequest and UnbindRequest?

After days of Google researching, Reading The F* Spring Security Manual and testing, I'm becoming desperate ...
The context : I'm implementing a Micro-Services architecture with Eureka etc...
I implemented an Auth Service which works very well with a MySQL authentication database. But now, I want to join my company LDAP through an OpenLDAP who works adequately.
So, I'm trying to join the LDAP with my spring security authentication.
The code of my configure() method (I replaced my company and domain name, the account {0} is "test"):
auth.ldapAuthentication()
.contextSource()
.url("ldap://myldap/ou=users,dc=mydomain,dc=mycompany")
.and()
.userDnPatterns("cn={0}");
I also tried different ways to write this and all the time, I get Bad Credentials or LDAP 32 error. With userDnPattern, usersearchbase method, passwordcompare, passwordencoder and others. I also tried to put DC in the root() method and the OU in the group...() method, no change (I think in fact that Spring Security sort these parameters smartly before sending LDAP Requests). To be honest, I tried 357654 differents ways to write the configure() method ...
The problem is that : When I put the same config, credentials, domains ... in an LDAP explorer software, it works correctly.
So I monitored LDAP networks exchanges with Wireshark and I saw this :
Wireshark screen
As we can see, there's 8 requests exchanged. The first 5 are OK. It find my account "test" correctly. But there's three over requests (with the unbindRequest which going back).
The problem is that Spring give me the result of the last request and say me the account doesn't exist or the credentials don't work, etc...
Have you got a clue for this ? Do you know how Spring Security works to question LDAP ? How can I do to contact my LDAP adequately with the framework ?
Thank you for reading.
Help me Stack Overflow, you're my only hope ...
I finally found the problem and got a solution.
My enterprise LDAP is an LDAP above an AD.
And this LDAP+AD needs Bind Authentication and doesn't authorize anonymous bind then authentication.
In Spring Security, there's an object which can do this : BindAuthenticator
This is how I try to use it (and it works).
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(ldapAuthenticationProvider()).userDetailsService(userDetailsService());
}
#Bean
public LdapAuthenticationProvider ldapAuthenticationProvider() throws Exception {
LdapAuthenticationProvider lAP = new LdapAuthenticationProvider(ldapAuthenticator(), ldapAuthoritiesPopulator());
return lAP;
}
#Bean
public LdapContextSource ldapContextSource() throws Exception {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource([URL of the LDAP]);
return contextSource;
}
#Bean
public LdapAuthenticator ldapAuthenticator() throws Exception {
BindAuthenticator authenticator = new BindAuthenticator(ldapContextSource());
authenticator.setUserDnPatterns(new String[] {"CN={0},"+[MY ENTERPRISE LDAP FILTER]});
return authenticator;
}
Hope this sample code will help some people ...
Thank you !

Authentication in Spring MVC via REST

I've been looking for a way to authenticate a user via REST controller (URL params).
The closest thing to do so is the following:
#Controller
#RequestMapping(value="/api/user")
public class UserController extends BaseJSONController{
static Logger sLogger = Logger.getLogger(UserController.class);
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody String login(#RequestParam(value="username") String user, #RequestParam(value="password") String pass) throws JSONException {
Authentication userAuth = new UsernamePasswordAuthenticationToken(user, pass);
MyCellebriteAuthenticationProvider MCAP = new MyCellebriteAuthenticationProvider();
if (MCAP.authenticate(userAuth) == null){
response.put("isOk", false);
}
else{
SecurityContextHolder.getContext().setAuthentication(userAuth);
response.put("isOk", true);
response.put("token", "1234");
}
return response.toString();
}
}
However, this doesn't create a cookie.
Any idea or a better way to implement what I want to achieve?
Firstly, you should not do this manually:
SecurityContextHolder.getContext().setAuthentication(userAuth)
It is better to employ special filter responsible for authentication, setting security context and clearing it after request is handled. By default Spring Security uses thread locals to store security context so if you don't remove it after client invocation, another client can be automatically logged in as someone else. Remember that server threads are often reused for different request by different clients.
Secondly, I would recommend using basic or digest authentication for your RESTful web service. Both are supported by Spring Security. More in docs http://static.springsource.org/spring-security/site/docs/3.1.x/reference/basic.html
And finally, remember that RESTful web service should be stateless.
Also remember that Spring Security documentation is your friend. :-)

Resources