Spring OAuth2 Client Credentials with UI - spring

I'm in the process of breaking apart a monolith into microservices. The new microservices are being written with Spring using Spring Security and OAuth2. The monolith uses its own custom security that is not spring security, and for now the users will still be logging into the monolith using this homegrown security. The idea is that the new MS apps will have their own user base, and the monolith app itself will be a "user" of these Services. I've successfully set up an OAuth2 Auth Server to get this working and I'm able to log in with Client Credentials to access the REST APIs.
The problem is that the Microservices also include their own UIs which will need to be accessed both directly by admins (using the new Microservice users and a login page) and through the monolith (hopefully using client credentials so that the monolith users do not have to log in a second time). I have the first of these working, I can access the new UIs, I hit the login page on the OAuth server, and then I'm redirected back to the new UIs and authenticated & authorized.
My expectation from the is that I can log in to the OAuth server with the client credentials behind the scenes and then use the auth token to have the front end users already authenticated on the front end.
My question is - what should I be looking at to implement to get the client credentials login to bypass the login page when coming in through the UI? Using Postman, I've gone to http://myauthapp/oauth/token with the credentials and gotten an access token. Then, I thought I could perhaps just GET the protected UI url (http://mymicroservice/ui) with the header "Authorization: Bearer " and I was still redirected to the login page.
On the UI app:
#Configuration
#EnableOAuth2Client
protected static class ResourceConfiguration {
#Bean
public OAuth2ProtectedResourceDetails secure() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("secure/ui");
details.setClientId("acme");
details.setClientSecret("acmesecret");
details.setAccessTokenUri("http://myoauthserver/secure/oauth/token");
details.setUserAuthorizationUri("http://myoauthserver/secure/oauth/authorize");
details.setScope(Arrays.asList("read", "write"));
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate secureRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(secure(), clientContext);
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(
Arrays.<AccessTokenProvider> asList(
new AuthorizationCodeAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(),
new ClientCredentialsAccessTokenProvider())
);
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
}
SecurityConfig:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientContextFilter oAuth2ClientContextFilter;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/ui").hasRole("USER")
.and()
.httpBasic()
.authenticationEntryPoint(oauth2AuthenticationEntryPoint());
}
private LoginUrlAuthenticationEntryPoint oauth2AuthenticationEntryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
}

Related

Spring boot with keycloak add api key auth for specific endpoint

in spring boot app I have:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(jsr250Enabled = true)
#Slf4j
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Autowired
List<String> aIPWhiteList;
#Autowired
List<String> bIPWhiteList;
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
String aIPAddressesFilterStr = defineIPFilters(aIPWhiteList);
String bIPAddressesPFilterStr = defineIPFiltersbIPWhiteList);
http.authorizeRequests()
.antMatchers("/order/a/**").access(aIPAddressesFilterStr)
.antMatchers("/b/order").access(bIPAddressesFilterStr)
.anyRequest().permitAll();
http.cors().and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
private String defineP4IPFilters(List<String> whiteList) {
StringBuilder ipAddressesFilterStr = new StringBuilder();
for (String ip: whiteList) {
ipAddressesFilterStr.append("hasIpAddress('").append(ip).append("') or ");
}
return ipAddressesFilterStr.substring(0, ipAddressesFilterStr.length() - 4);
}
}
I wonder how can I for this "b/order" make another auth, based on API Key stored in headers.
Basically only for this 1 endpoint I want authorize users differently.
Other endpoints are authorized by keycloak, are done from registered users.
But here I would like to auth it only by api key that is static.
any ideas ?
thanks!
I have two ideas which should save you quite some trouble, even if does not answer directly your question:
Do not use KeycloakWebSecurityConfigurerAdapter
It is part of the (very) deprecated Keycloak adapters for spring. Use spring-boot-starter-oauth2-resource-server instead. Refer to those tutorials for various ways to do it (with Keycloak)
Use OAuth2 client-credentials flow in place of API key
It serves that exact purpose: authenticate a trusted programmatic clients with "static" secrets.
With Keycloak, just declare "confidential" clients ("Client authentication" set to "On" and "Service Accounts Roles" enabled). Secret is to be retrieved from a "credentials" tab for this clients in Keycloak admin console. You can then define and assign different roles for each client if needed (such roles will appear in access-tokens, so you'll be able to use it in spring-security access control decisions)
Such clients will authorize their requests to resource-server(s) with access-tokens issued by your Keycloak instance just as other clients (used by humans) do. Only the protocol to get tokens (OAuth2 flow) differs: client-credentials for "robots" and authorization-code for "humans".
From the resource-server point of view, there will be absolutely no difference: all requests will be authorized with access-tokens issued by the same authorization-server => no need for a different authentication mechanism on some endpoints, just apply regular role-based access-control or wahtever else written with spring-security expressions.

How to secure a Spring Boot REST API

How do I secure my Spring REST API? I would like clients (On other domains/apps) to login/register to my API via google OAuth2 or simple username & password in a POST request and for my API to then return something they can use to authenticate following requests to secured endpoints.
I'm having a hard time finding solutions, I have kinda improvised a registration/login
with the following class.
My problems are 2:
I'm returning a cookie JSESSIONID which is going to a client on another domain, or a mobile app, what else could I return and how do I configure that?
Spring auto-generates login and logout pages, I don't need them since the app is just a REST API web service
#Order(2)
#Configuration
static class ResourceSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService
#Autowired
PasswordEncoder passwordEncoder
DaoAuthenticationProvider authProvider
/**
* Initialize the daoAuthenticationProvider
*/
#PostConstruct
void init() {
authProvider = new DaoAuthenticationProvider()
authProvider.setUserDetailsService(userService)
authProvider.setPasswordEncoder(passwordEncoder)
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// Disable auto config
http.httpBasic().disable()
// Authentication Provider
http.authenticationProvider(authProvider)
http.csrf().disable()
http.authorizeRequests().antMatchers('/api/users/auth/**').permitAll()
http.authorizeRequests().antMatchers("/login").denyAll()
http.authorizeRequests().anyRequest().authenticated()
http.formLogin().loginProcessingUrl('/api/users/auth/login')
http.logout().logoutUrl('/api/users/logout').invalidateHttpSession(true)
// Session Management
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
// Cors Configuration
CorsConfiguration corsConfiguration = new CorsConfiguration()
corsConfiguration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"))
corsConfiguration.setAllowedOrigins(List.of("*"))
corsConfiguration.setAllowedMethods(List.of("GET", "POST", 'OPTIONS'))
corsConfiguration.setExposedHeaders(List.of("Authorization"))
http.cors().configurationSource(request -> corsConfiguration)
}
}
I'm okay with users sending POST requests to /login and /register, but I need to configure something better than a JSESSIONID cookie in my response.
Is there a way to do everything with Google OAuth2? (That'd be optimal, a frontend can just login to Google with the google login react-component and then send me some kind of 'token' I can send to back to Google to verify their identity)

Adding support for multi-tenancy in Spring Boot application using Spring Security

I am new to Spring Security and Oauth2. In my Spring Boot application, I have implemented authentication with OAuth2 for one tenant. Now I am trying to multi-tenancy in my Spring Boot application. From the answer to the previous post: OAUTH2 user service with Custom Authentication Providers, I have implemented two security configurations in order to support two tenants: Tenant1 and Tenant2 as follows:
Custom OAuth2 user service is as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
...
}
}
Tenant 1 security configuration is as follows:
#Configuration
public class Tenant1SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customOAuth2UserService) {
this.customOAuth2UserService = customOAuth2UserService;
}
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.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("ADMIN")
.antMatchers("/tenant1/**").authenticated()
.and()
.oauth2Login()
.userInfoEndpoint().userService(oauth2UserService());
http
.cors().disable();
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return customOAuth2UserService;
}
}
Tenant 2 security configuration is as follows:
#Order(90)
#Configuration
public class Tenant2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new AntPathRequestMatcher("/tenant2/**"))
.csrf().disable()
.authorizeRequests()
.antMatchers("/tenant2/**").hasAuthority("USER")
.and()
.httpBasic();
http
.cors().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER");
}
}
application properties are as given below:
clientApp.name=myapp
spring.security.oauth2.client.registration.keycloak.client-id=abcd
spring.security.oauth2.client.registration.keycloak.client-name=Auth Server
spring.security.oauth2.client.registration.keycloak.scope=api
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.client-authentication-method=basic
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
myapp.oauth2.path=https://my.app.com/oauth2/
spring.security.oauth2.client.provider.keycloak.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.keycloak.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.keycloak.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.keycloak.user-name-attribute=name
Basically, the intent of my application is B2B. So if I want to onboard a new business entity B as a tenant of my application, plugin its authentication provider, all its existing users should get authenticated seamlessly.
So, in view of the above, I have thought of the approach (though I am not sure if it's the best approach) as follows:
There can be a single endpoint for all the tenants i.e. there can be a common login page for all the users regardless of the tenant. On this login page, there can be the provision for the users to enter only email IDs.
The tenant ID can be determined from the email ID entered by the user.
Based on tenant ID, authentication provider of associated tenant ID gets invoked in order to authenticate the user of associated tenant.
On successful authentication, redirect to the home page for the associated tenant as: https://my.app.com/<tenant-id>/
In addition to the above, I would like to build a setup, where my application has quite a few, say, 40 tenants, out of which say 20 tenants use OAuth2, 10 uses basic auth and 10 uses form login.
Here in order to implement the above type of functionality, from Multi tenancy for spring security, it seems I have to support one authentication method, add tenant ID to authentication token and then create an adapter to other authentication methods, as needed.
But, in this regard, I did not find any concrete idea in any post so far on what changes should I do in the existing code base in order to achieve this.
Could anyone please help here?

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 OAuth - Reload resourceIds and authorities of authentication

I just apply Spring Boot and Spring Cloud to build a microservice system. And I also apply Spring Oauth to it. Honestly, everything is perfect. Spring does a great job in it.
In this system, I have a microservice project does the job of an OAuth server, using JDBC datasource, and I using Permission based for UserDetails authorities (1 User has several Permissions). There are several microservice project does the jobs of Resource server (expose Rest api using Jersey), access security is based on Permissions of Authentication of OAuth bearer token.
Resource Server OAuth config class is something like this
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/restservice/object/list")
.hasAuthority("PERMISSION_VIEW_OBJECT_LIST");
// ...
}
#Override
public void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.resourceId("abc-resource-id")
.tokenStore(new JdbcTokenStore(dataSource()));
}
#Bean
#ConfigurationProperties(prefix = "oauth2.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
Everything is great! But I encounter 2 problems:
If I add a new microservice project as a new resourceId, and I append resourceId value to RESOURCE_IDS in table OAUTH_CLIENT_DETAILS of the OAuth client, all requests to Rest API of new resource service return error something like this
{"error":"access_denied","error_description":"Invalid token does not contain resource id (xyz-resource-id)"}
This happens even when user logout and re-login to obtain new access token. It only works if I go to delete records of the Access token and Refresh token int table OAUTH_ACCESS_TOKEN and OAUTH_REFRESH_TOKEN in database.
If at runtime, Permission of a User is changed, the authorities of authentication is not reloaded, I see that AUTHENTICATION value of the Access Token in table OAUTH_ACCESS_TOKEN still contains old Authorities before Permission is changed. In this case, User must logout and re-login to obtain new Access Token with changed authorities.
So, are there any ways to fix these 2 problems.
I'm using Spring Cloud Brixton.SR4 and Spring Boot 1.3.5.RELEASE.
If you are using the default Spring JdbcTokenStore, then the users authentication is serialised and stored with the access/refresh token when the user authenticates and retrieves their token for the first time.
Each time the token is used to authenticate, it is this stored authentication that is loaded which is why changes to the user permissions or the addition of extra resources is not reflected in the users permissions.
In order to add in some checking on this, you can extend DefaultTokenServices and override the loadAuthentication(String accessTokenValue) method to perform your own checks once the users authentication is loaded from the token store.
This may not be the ideal way of doing this, but it is the only way we've found of doing it so far.
To override DefaultTokenServices, add the follwoing bean method to you AuthorizationServerConfigurerAdapter config class:
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Bean
public AuthorizationServerTokenServices authorizationServerTokenServices() throws Exception {
// Where YourTokenServices extends DefaultTokenServices
YourTokenServices tokenServices = new YourTokenServices();
tokenServices.setTokenStore(tokenStore);
tokenServices.setClientDetailsService(clientDetailsService);
return tokenServices;
}
}
I resolved reload problem this way.
#Bean
public ClientDetailsService jdbcClientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}

Resources