Spring Security OAuth2 clientId and clientSecret - spring

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.

Related

Can Spring OAuth2 ClientDetailsServiceConfigurer authenticates with database?

I am new to Spring Boot OAuth2 framework.
I have the following working when authenticating incoming requests to ask for token.
The "withClient" and "secret" are both hard-coded and I want that to query against database
like MySQL. I like different clients to have different login/secret pairs.
Is this possible? Can someone provide example? thanks
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientA")
.secret("secret")
.accessTokenValiditySeconds(2000) // expire time for access token
.refreshTokenValiditySeconds(-1) // expire time for refresh token
.scopes("read", "write") // scope related to resource server
.authorizedGrantTypes("password");
}

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.

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;

Spring OAuth2.0 - Dynamically register OAuth2.0 client

I am working on setting up an OAuth2.0 authorization server using Spring security. I want to know if there is a way to dynamically register an OAuth2.0 client after the OAuth2.0 authorization server is up and running?
Basically, I know that I can register a client while configuring the OAuth2.0 server by extending the AuthorizationServerConfigurerAdapter and overriding the configure method to add the client details in memory. However, this way the client is pre-registered and I would like to know how to dynamically add the client details.
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients.inMemory()
.withClient(CLIENT_ID)
.secret(CLIENT_SECRET)
.authorizedGrantTypes("authorization_code", "implicit")
.redirectUris("http://junk/")
.scopes("cn")
.accessTokenValiditySeconds(600);
// #formatter:on
}
You should be able to just use the JdbcClientDetails (there are even convenience methods similar to the in memory ones):
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource)
.passwordEncoder(passwordEncoder)
.withClient("my-trusted-client")
... etc.
(Code taken from here: https://github.com/spring-projects/spring-security-oauth/blob/master/tests/annotation/jdbc/src/main/java/demo/Application.java#L102.) Then you have a database with data you can change at runtime as much as you want.

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

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
}
}

Resources