spring cloud gateway + spring security and multiple oauth2 client - spring-security-oauth2

I'm going to put a spring cloud gateway in front of some existing web applications which are already using keycloak as their identity provider and I want to authenticate the incoming requests inside the gateway. currently, each web application is already configured with the proper client-id and it redirects to the keycloak with the proper values. now, the gateway must do the authorization-code flow instead of each application, so it has to know in advance which client-is is for which requested url.
so, I was investigating how to implement it and I'm still here without any proper solution.
what is the solution for it? or, is it really a gateway responsibility to do that?

You can have a look to this answer:
How to create Spring Cloud gateway filter to add client-credentials access token?
In order to support different client-ids (secrets, token-uris and so on), you can just define multiple configurations in the spring.security.oauth2.client .registration section and make the clientid dynamic in the Oauth2ClientGatewayFilter class:
String clientId = ...
OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientId)
.principal("myPrincipal").build();

Actually, I found a solution however I'm not sure whether it's the best one.
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange().pathMatchers("/actuator/**").permitAll().and()
.authorizeExchange().anyExchange().authenticated().and().csrf().disable().oauth2Login()
.and()
.exceptionHandling().authenticationEntryPoint(createEntryPoints())
.and()
.oauth2ResourceServer().jwt()
.jwtAuthenticationConverter(grantedAuthoritiesExtractor());
return http.build();
}
public ServerAuthenticationEntryPoint createEntryPoints() {
List<DelegateEntry> entryPoints = new ArrayList<>();
entryPoints
.add(new DelegateEntry(ServerWebExchangeMatchers.pathMatchers("/app1"),
new RedirectServerAuthenticationEntryPoint("/oauth2/authorization/client1")));
//other clients will be added here
DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint(
entryPoints);
defaultEntryPoint.setDefaultEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED));
return defaultEntryPoint;
}
so the client1 will be used for the /app1 and so on.
as i said earlier i'm not sure and there might be better solution for that.

Related

How to secure a MVC application with OAuth2 using Spring?

Sorry, my English.
I have an application I can login in the usual way.
#Configuration
#EnableWebSecurity
public class LoginSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
System.out.println("LoginSecurityConfig :: configure");
auth.jdbcAuthentication().dataSource( getDataSource() )
.passwordEncoder( new BCryptPasswordEncoder(16) )
.usersByUsernameQuery(
"select user_name as username,password,enabled from users where user_name=?")
.authoritiesByUsernameQuery(
"select user_name as username, role_name from users_roles ur join users u on ur.user_id = u.user_id and u.user_name = ?");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login*").anonymous()
.antMatchers("/resources/**").permitAll()
.antMatchers("/fotos/**").permitAll()
.antMatchers("/users").access("hasRole('ROLE_ADMIN')")
.antMatchers("/user").access("hasRole('ROLE_ADMIN')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/loginPage")
.defaultSuccessUrl("/home", true)
.failureUrl("/loginPage?error=true")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.logoutSuccessUrl("/loginPage")
.invalidateHttpSession(true);
}
}
Using this I can try to access any secured resource and the system sends me to the loginPage where I can post username and password to the internal login controller then I have the Principal and can access the secured resources ( home, users, user ). Working fine.
But... I need to remove the user control database stuff and use OAuth2 to allow the same kind of access. I don't want to have any users in my database anymore. I need a login screen and then a token request like http://myserver/oauth/token?grant_type=password&username=admin&password=admin passing client_id and client_secret in Basic. I know how to do the "get token" part and my server is working fine and give me the token and refresh token but only using Postman because I have no idea how to use it in my web application code. All tutorials I've found are using both Server and Client in the same application and actually don't show how to consume an OAuth2 remote server.
Already try to use this. It is an excellent tutorial and very near to what I need but too complex to me.
I have this code and understand it can use the server and issue a token using the client credentials, but don't know how to give to the user a login screen and take his credentials to complete the request (the GET part).
#Configuration
#EnableResourceServer
public class OAuth2ResourceServerConfigRemoteTokenService extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests().anyRequest().permitAll();
}
#Primary
#Bean
public RemoteTokenServices tokenServices() {
final RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl("http://myoauthserver/oauth/check_token");
tokenService.setClientId("clientid");
tokenService.setClientSecret("password");
return tokenService;
}
}
so... how can I secure my system, take login and password from the user and use this code to control credentials like I was using usual database method?
Or OAuth2 is only for secure REST API?
Please be newbie friendly because I'm not very comfortable using Spring.
I’ve been working on this myself recently, and I wish I could say I have a simple answer, but I don’t. I would have to start by asking questions like, is this a web application (JSP etc) or a REST API used by a web application, or a REST API used by a mobile app, etc etc.
The reason this is important is that you first have to select one of the OAuth2 profiles and grant types, and they all have different requirements and configuration in Spring.
Also, are you trying to integrate with a third party OAuth2 authentication provider (e.g. Facebook) or is your application acting as both the authentication provider (where login and password validation occurs) and the protected resource (where the web page requests or API calls go to)?
So I guess the best I can do is assign you some homework:
(1) Read about the various OAuth2 profiles and determine which one best fits your application, and learn all the terminology (like, what is a client secret?).
This is definitely NOT one of those cases where you can just cut and paste example code without understanding it. If you don’t have a reasonable understanding of how OAuth2 works you are going to have a lot of difficulty.
Also: we’re talking about SECURITY here so doing stuff without understand it is a very bad idea. If you aren’t careful, you may think it’s working but in fact you’re leaving yourself wide open to attacks.
(2) if you are not familiar with Spring Framework Security you’ll need a basic grounding in that to understand what you’re doing.
(3) Once you have an idea which profile you’ll use, use that in a google search, e.g. “Spring oauth2 implicit grant” to find an example tailored for that profile.
There are a few out there and that’s a good place to start though I found I was not able to take any of the examples directly over to my application because of subtle differences in their assumptions and my application.
The Spring reference guide is helpful also but doesn’t necessarily give all the details for all the issues you may encounter. Finally, try to implement with your application.
You’ll want some good tools to send requests to your app (I like PostMan for that purpose) so you can inspect the data going back and forth. OAuth2 involves a complex series of HTTP redirects so testing can be a bit difficult.
Also, be patient. I consider myself a Spring expert and it still took me a few days to get things fully working the way I wanted. Note that there is actually VERY LITTLE code you end up writing, but getting the small amount of code exactly right is what’s difficult.
Simple as 1,2,3 ...
Just change a little my OAuth2 server to accept oauth/authorize method.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
and create a custom login form. Now all clients (web applications) can login into it.
Here you can find a sample client and a more details: http://www.baeldung.com/sso-spring-security-oauth2
Also you can check my entire server and client at my github repo:
https://github.com/icemagno/geoinfra/cerberus
and
https://github.com/icemagno/geoinfra/atlas
It is in pt_BR

How can I (simply) enable CORS on my Azure webrole API

I have an Azure webrole which is running an API service. I'm trying to enable CORS so that the API can be consumed by browser scripts. There are a quite a few questions that refer to enabling CORS on web-api applications but I haven't found one that gives an answer for webroles.
I've tried adding the magic customheaders block from this answer to my web.config but that doesn't work.
This document from Microsoft implies that the Microsoft.AspNet.Cors nuget package may be used but it's unclear to me how to get hold of the HttpConfiguration from within a webrole OnStart method. It also seems odd that I have to decorate every one of my API methods. Is there not a single 'switch' I can flick to enable CORS for the entire service?
Related questions...
What's the easiest way to verify that CORS is actually enabled? At the moment I'm using a Blazor PostJsonAsync call and relying on that to pass but it's getting pretty tedious repeatedly reconfiguring and uploading the role to Azure to try out changes.
Bigger question...am I fighting against the tide using a webrole? Much of the documentation refers to web-api and web-apps. Maybe these are the future and webroles are deprecated?
I would also recommend moving over to webapps. However, you might also get it to work with web roles and how you apply cors there also works for webapps if you use OWIN.
You might host your API in the web role like this:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/hosting-aspnet-web-api/host-aspnet-web-api-in-an-azure-worker-role
This gives you the HttpConfiguration you need (Startup.cs).
It also seems odd that I have to decorate every one of my API methods. Is there not a single 'switch' I can flick to enable CORS for the entire service?
You can use an ICorsPolicyProvider to enable it everywhere:
// in startup.cs
config.EnableCors(new AllowAllCorsPolicyProvider());
public class AllowAllCorsPolicyProvider : ICorsPolicyProvider
{
readonly CorsPolicy _CorsPolicy;
public AllowAllCorsPolicyProvider()
{
_CorsPolicy = new CorsPolicy {AllowAnyHeader = true, AllowAnyMethod = true, AllowAnyOrigin = true};
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(_CorsPolicy);
}

Spring-Security-OAuth2 - how to add fields to access token request?

I have a Spring Boot application, that is using Spring Security with OAuth 2.0. Currently, it is operating against an Authentication Server based on Spring Example code. However, running our own Auth Server has always been a short-term target to facilitate development, not a long-term goal. We have been using the authorization_code grant type and would like to continue using that, irrespective of the Auth Server implementation.
I am attempting to make changes to use OAuth 2.0 Endpoints in Azure Active Directory, to behave as our Authentication Server. So far, I have a successful call to the /authorize endpoint. But the call to get the /token fails with an invalid request error. I can see the requests going out.
It appears that parameters that Azure states as mandatory are not being populated in the POST request. Looking at the Azure doco, it expects the client_id to be defined in the body of the message posted to the endpoint, and that is not added, by default, by Spring.
Can anyone point me in the right direction for how I can add fields to the Form Map that is used when constructing the Access Token request? I can see where the AccessTokenRequest object is being setup in OAuth2ClientConfiguration....
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
protected AccessTokenRequest accessTokenRequest(#Value("#{request.parameterMap}")
Map<String, String[]> parameters, #Value("#{request.getAttribute('currentUri')}")
String currentUri) {
DefaultAccessTokenRequest request = new DefaultAccessTokenRequest(parameters);
request.setCurrentUri(currentUri);
return request;
}
Should I be trying to define the map in a request.parameterMap spring property? If so, I'm not too sure how that works.
Or should I be using one of the interfaces defined in the AuthorizationServerConfigurerAdapter class?
I have the information to include when sending the AccessTokenRequest, I just don't know the best way to configure Spring to include it? Thanks for any help.
Actually, I found this out. I needed to change the client authentication scheme. Simply adding the following to my application properties added the client_id to the form....
security.oauth2.client.clientAuthenticationScheme=form
If you're using yaml, then yaml-ize it. Thank you Spring!

Where to hook up authentication in Grizzly?

I'm using a Grizzly HttpServer which has two HttpHandler instances registered:
under /api/* there is an Jersey REST - style application offering the API of the product, and
under /* there is an StaticHttpHandler which serves static HTML / JavaScript content (which, among other things, talks to the API under /api/
For authentication I'm currently securing only the API using a Jersey ContainerRequestFilter implementing HTTP Basic Auth, which looks quite similar to what is presented in another SO question.
But as requirements changed, now I'd like to require authentication for all requests hitting the server. So I'd like to move the authentication one level up, from Jersey to Grizzly. Unfortunately, I'm completely lost figuring out where I can hook up a "request filter" (or whatever it is called) in Grizzly. Can someone point me to the relevant API to accomplish this?
The easiest solution would leverage the Grizzly embedded Servlet support.
This of course would mean you'd need to do a little work to migrate your current HttpHandler logic over to Servlets - but that really shouldn't be too difficult as the HttpHandler API is very similar.
I'll give some high level points on doing this.
HttpServer server = HttpServlet.createSimpleServer(<docroot>, <host>, <port>);
// use "" for <context path> if you want the context path to be /
WebappContext ctx = new WebappContext(<logical name>, <context path>);
// do some Jersey initialization here
// Register the Servlets that were converted from HttpHandlers
ServletRegistration s1 = ctx.addServlet(<servlet name>, <Servlet instance or class name>);
s1.addMapping(<url pattern for s1>);
// Repeat for other Servlets ...
// Now for the authentication Filter ...
FilterRegistration reg = ctx.addFilter(<filter name>, <filter instance or class name>);
// Apply this filter to all requests
reg.addMapping(null, "/*");
// do any other additional initialization work ...
// "Deploy" ctx to the server.
ctx.deploy(server);
// start the server and test ...
NOTE: The dynamic registration of Servlets and Filters is based off the Servlet 3.0 API, so if you want information on how to deal with Servlet listeners, init parameters, etc., I would recommend reviewing the Servlet 3.0 javadocs.
NOTE2: The Grizzly Servlet implementation is not 100% compatible with the Servlet specification. It doesn't support standard Servlet annotations, or deployment of traditional Servlet web application archive deployment.
Lastly, there are examples of using the embedded Servlet API here
The "hookup" part can be done using a HttpServerProbe (tested with Grizzly 2.3.5):
srv.getServerConfiguration().getMonitoringConfig().getWebServerConfig()
.addProbes(new HttpServerProbe.Adapter() {
#Override
public void onRequestReceiveEvent(HttpServerFilter filter,
Connection connection, Request request) {
...
}
#Override
public void onRequestCompleteEvent(HttpServerFilter filter,
Connection connection, Response response) {
}
});
For the "linking" to the ContainerRequestFilter you might want to have a look at my question:
UnsupportedOperationException getUserPrincipal

Spring Security with HTTPS on CloudFoundry

I tried to access my application on CloudFoundry with the following configuration in the spring security xml
<intercept-url pattern="/signup*" access="permitAll" requires-channel="https" />
but it gives me error This webpage has a redirect loop
However when I changed it to requires-channel="http" I can see my page normally. In both cases I used https on my application. Is this the expected behavior ?
First of all, taking a step back, this (https://johnpfield.wordpress.com/2014/09/10/configuring-ssltls-for-cloud-foundry/) provides excellent context for the nature of the problem.
The key paragraph being
“The threat model here is that the entry point to the cloud is a high availability, secured proxy server.  Once the traffic traverses that perimeter, it is on a trusted subnet.  In fact, the actual  IP address and port number where the Tomcat application server are running are not visible from outside of the cloud. The only way to get an HTTP request to that port is to go via the secure proxy. This pattern is a well established best practice amongst security architecture practitioners.”
Therefore, we may not want or need SSL all the way down, but read on to see how to avoid the https redirect issue when using Spring Security deployed on Cloud Foundry.
You will have a load balancer, HAProxy or some kind of proxy terminating SSL at the boundary of your Cloud Foundry installation. As a convention, whatever you are using should be configured to set X-Forwarded-For and X-Forwarded-Proto headers. The request header “X-Forwarded-Proto" contains the value http or https depending on the original request and you need to use this header parameter for your security decisions further down the stack.
The cleanest way to do this is at the container level, so that Spring Security behaves the same independent of deployment container. Some typical options to configure this are as follows
1) Tomcat
Tomcat should be configured with a RemoteIPValve as described nicely here
The good news is that the Java buildpack for Cloud Foundry already does this for you as seen here
2) Spring Boot (Embedded Tomcat)
Because Tomcat is embedded, the Tomcat config in the Java buildpack will not be activated (see the buildpack Detection Criterion), and therefore some internal Spring Boot configuration is required. Luckily, it’s pretty trivial to configure as you would expect with Spring Boot and you can switch on Tomcat’s RemoteIPValve as explained here by simply defining
server.tomcat.remote_ip_header=x-forwarded-for
server.tomcat.protocol_header=x-forwarded-proto
Both approaches lead to the same outcome of the Tomcat valve overriding the ServletRequest.isSecure() behaviour so that the application has no knowledge of the usage of any proxying. Note that the valve will only be used when the “X-Forwarded-Proto" header is set.
Alternatively, if you really want to go low-level you can dig into the guts of Spring Security, as demonstrated here. As part of that effort, there are some useful findings on how to make the “X-Forwarded-Proto" header available via the Servlet API for other containers (WebLogic, JBoss, Jetty, Glassfish) shared on the comments of https://github.com/BroadleafCommerce/BroadleafCommerce/issues/424
As an additional note, CloudFlare can also act as the SSL-terminating reverse proxy (this is the recommended approach via PWS as discussed here) and it does indeed forward the relevant headers.
References
https://stackoverflow.com/a/28300485/752167
http://experts.hybris.com/answers/33612/view.html
https://github.com/cloudfoundry/java-buildpack/commit/540633bc932299ef4335fde16d4069349c66062e
https://support.run.pivotal.io/entries/24898367-Spring-security-with-https
http://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html#howto-use-tomcat-behind-a-proxy-server
I have the same issue when I tried to secure my pages with HTTPS using Spring Security.
From the discussion on CloudFoundry Support, seems they "terminate SSL connections at the router". See "Is it possible to visit my application via SSL (HTTPS)?".
And after more than a year, no further information I can find regarding this issue.
In case it's still useful ... I found this post gave the clue to solve something similar to this.
The problem was the org.springframework.security.web.access.channel.SecureChannelProcessor bean was using ServletRequest.isSecure() to decide whether to accept the connection or redirect, which was getting confused inside the cloud.
The following override to that bean seemed to do the job under BlueMix - not sure if the $WSSC request header will apply to all environments.
#Component
public class ChannelProcessorsPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof SecureChannelProcessor) {
final SecureChannelProcessor scp = (SecureChannelProcessor) bean;
return new ChannelProcessor() {
#Override
public void decide(FilterInvocation invocation,
Collection<ConfigAttribute> config) throws IOException,
ServletException {
HttpServletRequest httpRequest = invocation.getHttpRequest();
// Running under BlueMix (CloudFoundry in general?), the
// invocation.getHttpRequest().isSecure() in SecureChannelProcessor
// was always returning false
if ("https".equals(httpRequest.getHeader("$WSSC"))) {
return;
}
scp.decide(invocation, config);
}
#Override
public boolean supports(ConfigAttribute attribute) {
return scp.supports(attribute);
}
};
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
return bean;
}
}

Resources