Spring OAuth: Resource Server with Authorization Server backend - spring-security

I want to develop two independent services, one for the business stuff and one for the user authentication using Spring OAuth 2
Let's call them Business-Service and OAuth-Service.
Now I want the Business-Service delegate to the OAuth-Service if a request is not authenticated. The client application (an Android app) should not know about the OAuth-Service a priori, it should only be delegated to it by the Business-Service with an 302 HTTP redirect for non-authenticated request. To be precise, I want my API landing page to provide a link to http://businessservice.com/login and when my client app decides to follow this link, it gets redirected to the OAuth-Service.
If I annotate the Business-Service with #EnableOAuth2Resource , all of its resources are protected returning a 401 when I curl them without an access token. So far so good. If I provide an access token like this:
curl -v http://localhost:8667/resource/ -H "Authorization: Bearer $TOKEN"
I can access the resource. Still good.
However if I annotate the Business-Service with #EnableOAuth2Sso for enabling the redirection to the OAuth service, it looses the capability of accessing the resources with an access token (same curl as above), it only returns a 302 to the login page http://localhost:8667/login
If I use both annotations, the #EnableOAuth2Resource always seems to "win", as the authentication works but calling http://localhost:8667/login returns a 404.
So what is the right way to create a resource server that delegates to the auth server for non-authenticated calls?

After trying around for hours I now found a solution.
The Business Server (Resource Server) now looks as follows:
#SpringBootApplication
#EnableOAuth2Sso
#EnableOAuth2Resource
public class BusinessService {
public static void main(final String[] args) {
final ConfigurableApplicationContext context = SpringApplication.run(BusinessService.class, args);
}
}
with two configurations, one for the SSO:
#Configuration
public class OAuth2SsoConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void match(final RequestMatchers matchers) {
matchers.antMatchers("/");
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll();
}
}
and one for the Resource:
#Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/resource/**").and().authorizeRequests().anyRequest().authenticated().antMatchers("/").permitAll();
}
}
This results in the following:
curl -v http://localhost:8667/
returns
HTTP/1.1 200 OK
{"links":[{"rel":"login","href":"http://localhost:8667/login"}]}
curl -v http://localhost:8667/resource/
returns
HTTP/1.1 401 Unauthorized
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
curl -v http://localhost:8667/login
returns
HTTP/1.1 302 Found
Location: http://localhost:8666/user/oauth/authorize?client_id=clientId&redirect_uri=http%3A%2F%2Flocalhost%3A8667%2Flogin&response_type=code&state=YmmNO9
So my business servie is secured with as a resource server returning a 401 for all business resources. The root of the service is applicable for all clients so they can discover the login relation and if they follow this relation, they're redirected to the Authorization server

Related

Spring authorization server authenticate for each client

I'm trying to build an Identity Provider using Spring authorization-server that third party applications are going to use for FIM (federated identity management).
We want each OAuth client to require authentication (if a user tries to login with a different client they would need to authenticate for each client).
Out of the box the flow looks like this:
So there's 2 issues.
The /oauth2/authorize endpoint just checks whether or not the sessions principal is authenticated, it doesn't care or know which client the principal was meant for.
There's just a single /login endpoint, so during authentication it doesn't know which client is used.
My best bet here is that I should:
Make the oauth2/authorize endpoint redirection to /login include the query parameter client_id
Create a custom AuthenticationFilter that also adds the client_id to the User principal
Override the authorizationRequestConverter for the oauth2/authorize endpoint and validate that the client in the request is the same as the client stored on the authenticated principal
Am I missing anything or do anyone know of a simpler way of doing this?
Based on your last comment, it seems one possibility is to simply require authentication every time, or at least every time an authorization is requested. In that case, you could clear out the authentication after the authorization code is issued to the client, using a Filter. This doesn't seem ideal and will result in a poor user experience, but may achieve your requirement.
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// ...
// Add filter to remove the SecurityContext after successful authorization
http.addFilterAfter(new RemoveSecurityContextOnAuthorizationFilter(), LogoutFilter.class);
return http.build();
}
private static final class RemoveSecurityContextOnAuthorizationFilter extends OncePerRequestFilter {
private SecurityContextHolderStrategy securityContextHolderStrategy =
SecurityContextHolder.getContextHolderStrategy();
private final LogoutHandler logoutHandler = new CompositeLogoutHandler(
new CookieClearingLogoutHandler("JSESSIONID"),
new SecurityContextLogoutHandler()
);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request, response);
} finally {
String locationHeader = response.getHeader(HttpHeaders.LOCATION);
if (locationHeader != null) {
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
if (uriComponents.getQueryParams().containsKey("code")) {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
this.logoutHandler.logout(request, response, authentication);
}
}
}
}
}
// ...
}

Spring Boot Security Configuration OAuthSSO and ResourceServer

I have a WebApp consisting of 2 parts.
One is with a frontend (Vaadin) where i want the user to be Logged-In via OAuth2. I then Check whether the user has a certain Role or not. --> If user opens the URL he shall be redirected to the OAuthLogin automatically. --> This is working with the #EnableOAuthSso.
Second Part is the REST-API of the Application, which is found by anything under /api/*. fE. /api/devices
should give me a list if the Get-Request has a valid Bearer-Token. If the GET Request has no Bearer-Token or a wrong Role (Authority) if want to get a 403.
Now this is my configuration:
#Configuration
#EnableOAuth2Sso
public class ProdWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String ADMIN_ROLE= "role.global.admin";
private static final String READ_API_ROLE= "role.base.read.api";
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/login**", "/error**").permitAll()
.antMatchers("/*").hasAuthority(ADMIN_ROLE)
.antMatchers("/api/**").hasAnyAuthority(ADMIN_ROLE, READ_API_ROLE)
.and().logout().permitAll().logoutSuccessUrl(rootAuthUri + "/connect/endsession")
;
}
Now when opening for example /manageDevices in the Browser i get forced to be logged in via Auth-Code-Flow and everything works like as expected.
When i try to open /api/devices i also get forced to be logged in via Oauth. Even when i do send Http-Header with Authentication: Bearer xxxxx. Somehow it always forces me to the Login-Screen from my OAuth login.
application.properties these lines are defined:
base.rootauthuri=https://oauth2.mypage.ch
security.oauth2.client.clientId=client.base.parameters
security.oauth2.client.clientSecret=secret
security.oauth2.client.accessTokenUri=${base.rootauthuri}/connect/token
security.oauth2.client.userAuthorizationUri=${base.rootauthuri}/connect/authorize
security.oauth2.client.scope=openid,scope.base.parameters,role,offline_access
security.oauth2.client.clientAuthenticationScheme=form
security.oauth2.resource.userInfoUri=${base.rootauthuri}/connect/userinfo
How can i force everything under /api/* to not redirect to the AuthenticationForm but respond with 403 if no Bearer Token is sent. How can i make it to Check whether the Bearer-Token has Role "READ_API_ROLE" also.
I had the same question with SSO, I configured a ResourceServe for that:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private ResourceServerConfiguration configuration;
#PostConstruct
public void setSecurityConfigurerOrder() {
configuration.setOrder(3);
}
#Bean("resourceServerRequestMatcher")
public RequestMatcher resources() {
return new AntPathRequestMatcher("/api/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/v1/**") // this is free resource
.and().authorizeRequests()
.antMatchers("/api/**").permitAll() // This is free resource for mvc calls
// Usado para paths que necessitam de token bearer
.and().requestMatchers().antMatchers("/integration/**")
.and().authorizeRequests()
.antMatchers("/integration/**").authenticated(); // this is protected resource, it's necessary token
}
}
I not configure WebSecurityConfigurerAdapter in my project;
Check this:
Spring Boot 1.3.3 #EnableResourceServer and #EnableOAuth2Sso at the same time
https://www.baeldung.com/spring-security-oauth2-enable-resource-server-vs-enable-oauth2-sso

Spring Security OAuth2 - optional login with server check

I'm working on a Web Project with different Spring Boot WebMVC Clients. Some of this Clients needs a authorization and I solved it with a Spring Security OAuth2 Server. The authentication works fine and I had no problems. Some Clients didn't need an login and they are public for all.
Technical facts: All clients use a mix between Angular, jQuery and simple JSP's. All apps use Spring Security and the public app configuration is like this:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.antMatchers("/fonts/**").permitAll()
.anyRequest().authenticated();
}
Now my question: I plan to build a Login (or Logout) Button in the Header of all apps. In the apps with a required authentication is that no problem. I can check the principal is null or not. But how can I solve this in public apps. The principal is ever null and the client didn't check the authentication status with the server. I had some ideas to fix it but nothing is working. The best way would be a automatic check in Spring Boot. But how can I configure this? Maybe I can check it with JavaScript, but my shots also didn't work.
Maybe it would help - two of my apps:
https://www.planyourtrip.travel (public application)
https://profile.planyourtrip.travel (memberonly application)
UPDATE: Maybe a better example
If I configure a public app like this
#Configuration
#EnableOAuth2Sso
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated();
}
}
and the MVC Controller like this
#RequestMapping("/{([a-z]{2})}")
public ModelAndView start(final Principal principal) {
return new ModelAndView("start");
}
then is the Principal ever null. I think that is my Problem. I need a check with the OAuth Server and if i logged in is the principal set and if I'm not logged in it should be null.
If I had understood your question correctly, than you need that some URL pattern can be accessed without authentication. Than in that case you can use the following method to prevent authentication for certain URL patterns -
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/urlPattern");
}
permitAll() method defines that all the authenticated users can access mentioned URL pattern. So if you want some users to access some resources (URL) without authentication, than you have to use above method.

Return locked user in dropwizard auth

In dropwizard auth module, I want to return to the caller of my rest-api that the user is locked.
(the credentials might be correct, but the user is locked/disabled)
I've looked here: http://www.dropwizard.io/1.0.5/docs/manual/auth.html but cannot find anything related to it.
And authenticate method of Authenticator returns Optional of my T object, and the documentation says:
You should only throw an AuthenticationException if the authenticator
is unable to check the credentials (e.g., your database is down).
so, for some background, the reason you should only throw an AuthenticationException is because the filter provided by the good guys at DW only handles this exception. For all other exceptions, this is undefined.
For detail see: AuthFilter#authenticate
However, there is a really easy way to do what you want.
Looking at the DW code, you get this:
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
final BasicCredentials credentials =
getCredentials(requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
if (!authenticate(requestContext, credentials, SecurityContext.BASIC_AUTH)) {
throw new WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm));
}
}
What this code does is to call your Authenticator to resolve the user and then check if it is authenticated. If it is not, then it will call the UnauthorizedHandler to check out what response it needs. Sadly, the handler does not get the Principal passed in, so it can only return a static exception.
Now, if you wanted to hand-craft this, this would be your entrypoint. Instead of simply using the BasicCredentialAuthFilter they provide, you would write your own that does the right thing for you.
However, from the code snippet, we can see that all this filter does is to throw a WebApplicationException. So we can shortcut this.
Our Authenticator implementation can do the user-locked check beforehand and populate an exception for us to bypass this behaviour. This way, the downstream logic is preserved (react to the WebapplicationException which I believe is actually a jersey feautere (see: exception mapppers)).
So, consider this example:
public class AuthenticatorTest extends io.dropwizard.Application<Configuration> {
#Override
public void run(Configuration configuration, Environment environment) throws Exception {
environment.jersey().register(new MyHelloResource());
UserAuth a = new UserAuth();
environment.jersey().register(new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Principal>()
.setAuthenticator(a).setRealm("SUPER SECRET STUFF").buildAuthFilter()));
}
public static void main(String[] args) throws Exception {
new AuthenticatorTest().run("server", "/home/artur/dev/repo/sandbox/src/main/resources/config/test.yaml");
}
#Path("test")
#Produces(MediaType.APPLICATION_JSON)
public static class MyHelloResource {
#GET
#Path("asd")
#PermitAll
public String test(String x) {
return "Hello";
}
}
public static class UserAuth implements Authenticator<BasicCredentials, Principal> {
#Override
public Optional<Principal> authenticate(BasicCredentials credentials) throws AuthenticationException {
throw new WebApplicationException(Response.status(403).entity("User is blocked").build());
}
}
}
This code simply creates a new exception instead of authenticating the username. This results in this curl:
artur#pandaadb:/$ curl "artur:artur#localhost:9085/api/test/asd"
User is blockedartur#pandaadb:/$ curl "artur:artur#localhost:9085/api/test/asd" -v
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9085 (#0)
* Server auth using Basic with user 'artur'
> GET /api/test/asd HTTP/1.1
> Host: localhost:9085
> Authorization: Basic YXJ0dXI6YXJ0dXI=
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< Date: Thu, 12 Jan 2017 14:19:27 GMT
< Content-Type: application/json
< Content-Length: 15
<
* Connection #0 to host localhost left intact
User is blocked
Now, this is likely not the cleanest solution that you can do. If you need this to be quick, then you can just throw the exception out of the Authenticator. However, the correct approach would be:
Implement a new AuthFilter basing it off io.dropwizard.auth.AuthFilter
Overwrite the authenticate method on AuthFilter to check your user and throw the correct exception there.

Spring Oauth 2 SSO, Zuul and OpenAM integration

Introduction, Requirements:
right now i am writing a Single Page Application with AngularJS which talks to a Spring REST API. For security purposes I would like to setup a reverse proxy with zuul which proxies every request to the API and verifies that the user is authenticated. Also, if the user is not authenticated he should be redirected to an OpenAM instance (functioning as OAuth 2 Authorization Server). If the user is authenticated the request should be forwarded to the API with a Json Web Token (JWT) in the Header, containing at least the LDAP groups of the User.
In short I would like to have something like a API Gateway similar to the solution in this tutorial: https://spring.io/blog/2015/02/03/sso-with-oauth2-angular-js-and-spring-security-part-v
Status quo
I setup the Spring Cloud Security and Zuul with the following config:
server:
port: 9000
spring:
oauth2:
sso:
home:
secure: false
path: /,/**/*.html
client:
accessTokenUri: http://openam.example.org:8080/OpenAMTest/oauth2/access_token
userAuthorizationUri: http://openam.example.org:8080/OpenAMTest/oauth2/authorize
clientId: bearer-client
clientSecret: clientsecret
scope: openid profile
resource:
userInfoUri: http://openam.example.org:8080/OpenAMTest/oauth2/userinfo
zuul:
routes:
exampleApp:
path: /example-application/**
url: http://openam.example.org:8081/example-application
The Application class looks like the following:
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Sso
public class TestZuulProxy extends SpringBootServletInitializer {
public static void main(String[] args){
SpringApplication.run(TestZuulProxy.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class<TestZuulProxy> applicationClass = TestZuulProxy.class;
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.anyRequest();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html", "/home.html", "/")
.permitAll().anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
}
}
Now when i go to the "example-application" i get forwarded to the OpenAM authorization login screen. When I type in the credentials I can access the "example-application". Console log on the Gateway Service:
2015-06-22 17:14:10.911 INFO 6964 --- [nio-9000-exec-3] o.s.c.s.o.r.UserInfoTokenServices : Getting user info from: http://openam.example.org:8080/OpenAMTest/oauth2/userinfo
2015-06-22 17:14:10.953 INFO 6964 --- [nio-9000-exec-3] o.s.b.a.audit.listener.AuditListener : AuditEvent [timestamp=Mon Jun 22 17:14:10 CEST 2015, principal=Aaccf Amar, type=AUTHENTICATION_SUCCESS, data={details=remoteAddress=0:0:0:0:0:0:0:1, sessionId=<SESSION>, tokenType=BearertokenValue=<TOKEN>}]
Http-Header read by Zuul Filter:
authorization --- Bearer c2b75b5a-c026-4e07-b8b9-81e9162c9277
x-forwarded-host --- localhost:9000
x-forwarded-prefix --- /example-application
So something works! I have an access-token that gets forwarded to the REST-API.
Problem
1) This solution does not really meet my requirements, because I don't want the REST API to call the token-endpoint of OpenAM. I want that a JWT with the nessessary claims gets passed to the API in the Header. Should I create a JWT in the Gateway (e.g. Zuul Filter) manually or is there another solution?
2) In the solution above, when the access-token expires Zuul keeps forwarding me to the API. Why is this? Doesn't Spring Oauth2 checks if the access-token expires? how can I implement that?
3) I also tried to configure the tokenInfoUri in application.yml, but then I am getting a "405 Method Not Allowed" exception, because I think OpenAM expects a GET request on the tokeninfo-Endpoint. Can I customize this somehow? Which Classes do I need to override/customize to change the request.
If you have an advices, ideas or possible solutions, let me know!
Thank you!
If you want to use a JWT in your application, configure OpenAM as an OpenID Connect provider (OpenAM 12.0 or later). Once the user has authenticated OpenAM will issue a JWT with a number of claims about the user. Your SPA can pass that along in requests to your service tier.
If you want a gateway to enforce AuthN/ AuthZ on the users session, you can use something like ForgeRock's OpenIG. This can act as a policy enforcement point, and has the ability to introspect JWT tokens.

Resources