Authentication Server for App Gateway and Web App sharing the user base with Spring Boot - spring

I'm writing an App Gateway (REST-API) and a Web Application using Spring Boot. I've got a user database containing users and password hashes. Both applications will use the same user database, hence I want to have it in an isolated service. I've looked over https://spring.io/guides/tutorials/spring-security-and-angular-js/ which raised the idea to use an Auth Server via OAuth.
I'm quite unsure about the Request Flow for my use case. I think it will go App -> App Gateway (e.g. Login), which then does a request to /uaa/oauth/token with grant type password(since i got the user credentials). This request must contain client_id and client_secret and should return a token.
Is this concept correct for my usecase?
If so: How do I have to configure the Auth Server to use the Database instead of Basic Auth? All examples I found have a certain user / pw combination in application.properties
What would be the most idiomatic way to delegate Auth via username + pw to the Auth Server?
Edit: I found this to be very helpful: https://github.com/dsyer/sparklr-boot
Now I can do:
curl -H "Accept: application/json" my-trusted-client#localhost:8080/oauth/token -d grant_type=password -d username=user -d password=password
How do I wire some user backend into it? As far as I understand the following uses the default Spring Basic Auth settings.
application.yml:
security:
user:
password: password
Code:
#Configuration
#EnableAuthorizationServer
protected static class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients.inMemory().withClient("my-trusted-client")
.authorizedGrantTypes("password", "authorization_code",
"refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust").resourceIds("sparklr")
.accessTokenValiditySeconds(60).and()
.withClient("my-client-with-registered-redirect")
.authorizedGrantTypes("authorization_code").authorities("ROLE_CLIENT")
.scopes("read", "trust").resourceIds("sparklr")
.redirectUris("http://anywhere?key=value").and()
.withClient("my-client-with-secret")
.authorizedGrantTypes("client_credentials", "password")
.authorities("ROLE_CLIENT").scopes("read").resourceIds("sparklr")
.secret("secret");
// #formatter:on
}
}

Related

Connecting Spring Security OAuth2 with SAML SSO

We’re having a microservices architecture based on spring boot where we have multiple microservices talking to each other and also a Javascript UI that connects to the different microservices.
Since this is an internal application and we have the requirement to connect them to our SAML2 endpoint to provide SSO, I’m getting a bit of a headache to connect all of this together. Ideally the microservices use oAuth2 between themselves (JWT) and the UI, but User Authentication is done through SAML2
The following I want to achieve with this:
UI Clients talk to the microservices by using JWT
Microservices use JWT as well to talk to each other. When a user initiates a request to a microservice and that microservice needs more data from another one, it uses the users JWT token (this should be fairly easy to do).
Having one central authentication microservice which is responsible for generating new tokens and authenticate the user against the SAML endpoint.
Storing some SAML details (e.g. Roles) in the authentication microservice
So I have tried many different things. What I can say is the following:
Using OAuth between microservices and JWT works fine and is not really an issue (e.g. this link is a nice tutorial to set this up http://www.swisspush.org/security/2016/10/17/oauth2-in-depth-introduction-for-enterprises )
Using SAML with spring-security-saml-dsl is also straight forward and works pretty well
I have implemented JWT in combination of spring-security-saml-dsl and that works also well (similar to this: https://www.sylvainlemoine.com/2016/06/06/spring-saml2.0-websso-and-jwt-for-mobile-api/ except that I use spring-security-saml-dsl) which I don’t like because it uses to much custom code with all the filters, etc. but would be a way to go.
I guess where I struggle with is the connection points of oauth2 Resource Server and the SAML services.
Regarding SAML I have the following that works fine:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${security.saml2.metadata-url}")
String metadataUrl;
#Value("${server.ssl.key-alias}")
String keyAlias;
#Value("${server.ssl.key-store-password}")
String password;
#Value("${server.port}")
String port;
#Value("${server.ssl.key-store}")
String keyStoreFilePath;
#Autowired
SAMLUserDetailsService samlUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/**").authenticated()
.and().exceptionHandling()
.and()
.authorizeRequests()
.antMatchers("/saml*").permitAll()
.anyRequest().authenticated()
.and()
.apply(saml()).userDetailsService(samlUserDetailsService)
.serviceProvider()
.keyStore()
.storeFilePath("saml/keystore.jks")
.password(this.password)
.keyname(this.keyAlias)
.keyPassword(this.password)
.and()
.protocol("https")
.hostname(String.format("%s:%s", "localhost", this.port))
.basePath("/")
.and()
.identityProvider()
.metadataFilePath(this.metadataUrl);
}
}
and that works fine. so when I hit a protected endpoint I will get redirected and can login through saml. I get the userdetails then in the samlUserDetailsService.
Regarding oauth I have something like this:
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.tokenEnhancer(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("ABC"); //needs to be changed using certificates
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("refresh_token", "authorization_code")
.autoApprove(true)
.scopes("webapp")
.accessTokenValiditySeconds(60)
.refreshTokenValiditySeconds(3600);
}
}
This part also works fine with other micorservices where I have #EnableResourceServer
As far as I understand the OAuth part, the ClientDetailsServiceConfigurer just configures the client applications (in my case the other microservices) and I should use client_credentials kind of grant for this (but aren't sure). But how I would wire in the SAML part is not clear to me...
As an alternative I'm thinking about splitting this up. Creating a microservice that is an OAuth Authorization Service and another one that does the SAML bit. In this scenario, the SAML Microservice would connect to SAML and provide an endpoint like /me if the user is authenticated. The OAuth Authorization Service would then use the SAML Microservice to check if a user is Authenticated there and provide a token if that is the case. I would also do the same regarding refresh tokens.
As far as I understand this, I would implement this kind of logic in the
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {} method.
If there's a better approach, let me know!

Spring Security OAuth2 clientId and clientSecret

I am evaluating the Spring Security OAuth2 implementation. I am confused by clientId and clientSecret.
I follow https://spring.io/guides/tutorials/spring-security-and-angular-js/ to build auth server.
I can get generate code by
http://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com
I also can obtain the accesstoken by
curl acme:acmesecret#localhost:9999/uaa/oauth/token \
-d grant_type=authorization_code -d client_id=acme \
-d redirect_uri=http://example.com -d code=jYWioI
{"access_token":"2219199c-966e-4466-8b7e-12bb9038c9bb","token_type":"bearer","refresh_token":"d193caf4-5643-4988-9a4a-1c03c9d657aa","expires_in":43199,"scope":"openid"}
When getting access token, the clientId and clientSecret is required.
But if I have multiple clients, should I start multiple auth server? It cannot work in this way.
How do I build OAuth2 server without clientId and clientSecret?
The code is here: https://github.com/yigubigu/spring-security-auth
You can setup may clients
Ex In Memory :-
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("acme")
.secret("acmesecret")
.authorizedGrantTypes("authorization_code", "refresh_token",
"password").scopes("openid")
.and()
.withClient("xx")
.secret("xx")
.authorizedGrantTypes("xxx");
}
Or you can add Database record for client
REF - Spring oauth2 DB Schema
In order to achieve dynamic client registration, you need to store the credentials in database, instead of hardcoded configuration.
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource())
// ...
}
Please refer to this tutorial for more info.

Is it possible to get an access_token from Spring OAuth2 server without client secret?

I am using Spring Security's OAuth2 server implementation. I am trying to get the access_token from the servers' /oauth/token endpoint using the OAuth2 "Password" grant type by only supplying username and password and the client id without the client secret.
This works fine as long as I provide the client id and the client secret in the Authorization header of my HTTP request like so:
curl -u clientid:clientsecret http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"
Following the advice here: Spring OAuth2 disable HTTP Basic Auth for TokenEndpoint, I managed to disable HTTP Basic authentication for the /auth/token endpoint. But when I tried to get the access_token via cURL like so:
curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT"
I got a BadCredentialsException and could see the message:
Authentication failed: password does not match stored value
in my servers' log. At this point I was slightly irritated, because it was my understanding that this message only shows up when there's something wrong with the username and/or password, not the client id and/or secret. After additionally supplying the client secret in the cURL command like so:
curl http://myhost ... -d "grant_type=password&username=user&password=pw&client_id=OAUTH_CLIENT&client_secret=SECRET"
everything was fine again.
So does that mean I have to supply the client secret one way or another to access the /auth/token endpoint?
PS: I am aware of the fact that regarding security it is generally a good idea to protect this endpoint via HTTP Basic authentication, but there are some use cases where one would rather be able to do without.
Edit:
I seem to have found a way to omit the client secret. Here's my OAuth2 server configuration (notice the calls to allowFormAuthenticationForClients() and autoApprove(true)):
#Configuration
#EnableAuthorizationServer
class OAuth2Config extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
public OAuth2Config(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauth) throws Exception {
// allows access of /auth/token endpoint without HTTP Basic authentication
oauth.allowFormAuthenticationForClients();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("acme")
.autoApprove(true) // <- allows for client id only
.authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("openid");
}
}
Edit II:
The question here: Spring Security OAuth 2.0 - client secret always required for authorization code grant is very closely related to this one but deals with the OAuth2 grant type "Authorization Code", which results in a different workflow like the one you get with grant type "Password".
According to the specification (RFC 6749), if the client type of your application is public, a client secret is not required. On the contrary, if the client type is confidential, a client secret is required.
If Spring offers an API to set the client type, try to set the client type to public.
Spring Boot's implementation requires that a client-secret be passed in to authenticate. You can however override this by creating a bean of type AuthorizationServerConfigurer and configuring it yourself. This is the link to the documenation...
Use basic auth but leave the password empty.
In the implementation of AuthorizationServerConfigurerAdapter override configure and set password encoder to raw text encoder (do not use it as a default password encoder!).
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.passwordEncoder(plainTextPasswordEncoder())
.allowFormAuthenticationForClients();
}
private PasswordEncoder plainTextPasswordEncoder() {
return new PasswordEncoder() {
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return !StringUtils.hasText(encodedPassword) || passwordEncoder.matches(rawPassword, encodedPassword);
}
#Override
public String encode(CharSequence rawPassword) {
return passwordEncoder.encode(rawPassword);
}
};
}
}
Now, for OAuth client details (in memory or in a database), set the client secret to null. In this case, the client will be treated as public and will not require client_secret parameter. If you set client secret for OAuth client details (e.g. BCrypt hash), then the client will be treated as confidential. It will rely on default password encoder (e.g. BCrypt) and require client_secret parameter to be sent in order to obtain an access token.

Spring OAuth2(JWT tokens) multi-tenancy based on clientId

In my Spring Boot application I have a single back-end application that exposes REST API.
I'm going to consume this API from different client applications(for example written in AngularJS).
Based on different OAuth2 clientId I'd like to allow or restrict user access to different functionalities or data how it was described in my previous question Spring OAuth 2 + Spring Data Neo4j multi-tenancy
For example I have 3 different web domains:
example1.com
example2.com
example3.com
and for domain example1.com I'd like to allow user login only with clientapp1
My current login url:
http://localhost:8080/oauth/authorize?response_type=token&client_id=clientapp1&redirect_uri=http%3A%2F%2Flocalhost%3A8080
In my Spring OAuth2 server(with JWT tokens) I have configured multiple clients - clientapp1, clientapp2, clientapp3
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
.withClient("clientapp1")
.authorizedGrantTypes("password","refresh_token")
.authorities("ROLE_CLIENT")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret("123456")
.and()
.withClient("clientapp2")
.authorizedGrantTypes("implicit")
.scopes("read", "write")
.autoApprove(true)
.and()
.withClient("clientapp3")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(60);
// #formatter:on
}
}
The issue is that right now any user in the system can login into the applications with any clientId and thus can use for example domain3.com with clientapp1
I need the following behavior:
REST API calls from domain1.com must be only allowed with OAuth2 JWT token only with clientapp1 inside, for domain2.com only with clientapp2 and so on.
How to configure my application in order to be able only login user with a correct clientId for appropriate domain ?
Is it safe to store and directly use clientId value on client side(in user browser) ?
Also, if my approach is wrong - please suggest how to correctly implement multi-tenancy with OAuth2.

Why ClientDetailsServiceConfigurer is issuing tokens to users with ROLE_ADMIN when is it configured only for ROLE_USER?

I am using spring boot application with Oauth2 Security
I have configured ClientDetailsServiceConfigurer to use client01 client to issue tokens to only users who have ROLE_USER like this:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client01")
.scopes("read", "write")
.authorities("ROLE_USER")
.authorizedGrantTypes("password", "refresh_token")
.secret("password")
.accessTokenValiditySeconds(1800);
}
curl -X POST -vu client01:password 'http://localhost:8080/api/oauth/token?username=admin01&password=test&grant_type=password'
Why with this client I can also issue tokens to users with ROLE_ADMIN?
If I have wrong implementation, what would be the right one?
My goal is to create two clients:
- one client should allow to authenticate users with ROLE_ADMIN only;
- another client should allow to authenticate users with ROLE_USER only;

Resources