Allow user with admin role to consume all apis - spring

I configured my API security side using spring security :
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests(authRequests -> {
// Allow all resource to admin
authRequests.antMatchers("*").hasRole(RoleType.Fields.ADMIN);
// Permissions OPTIONS
authRequests.mvcMatchers(HttpMethod.OPTIONS).permitAll();
// Management endpoints
authRequests.mvcMatchers("/management/**", "/docs/**", "/webjars/**").permitAll();
// Business APIs
authRequests.anyRequest().authenticated();
});
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter());
for other roles, I created a filter to get authoritie, it works fine, the problem is that I want to give the ROLE_ADMIN access to all resource, when I used antMatchers("*") , the api gives 403 code.

Try to disable csrf protection:
http.csrf().disable();
and use anyRequest() instead of antMatchers("*"):
authRequests.anyRequest().hasRole(RoleType.Fields.ADMIN)

Related

Is there any implementation of OIDC Session Management and Logout mechanism in Spring Authorization Server for implementing Single Sing On?

I am trying to implement Single Sing On using Spring Authorization Server which is based on oAuth2 and OIDC for Authorization and Authentication respectively, but as per the Spring Authorization Server feature list OIDC support is not fully integrated. Mainly the session management and logout part, I couldn't find. Also if there's any workaround for implementing sso and logout?
Feature List URL: https://docs.spring.io/spring-authorization-server/docs/current/reference/html/overview.html
These features are still on the backlog but are not scheduled yet (as of the time of this writing). See #58 and #266 respectively to follow progress on these features.
Given that there are a number of pieces to the specifications for both of these features, I imagine it would be a bit of a hassle to attempt a fully spec-compliant implementation of them as extensions to SAS (though I'm sure it's possible). Instead, you might pick a minimal subset of the logout feature as a way to get started.
Using Spring Security configuration, you can configure a logout endpoint in a custom way. Here's some pseudocode to get you started:
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.logout((logout) -> logout
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout", "GET"),
new AntPathRequestMatcher("/logout", "POST")
))
.addLogoutHandler((request, response, authentication) -> {
// Logic to validate an id_token_hint, client_id, etc.
// Throw exception in case of invalid request
})
.logoutSuccessHandler((request, response, authentication) -> {
// Get state and calculate redirect for logout success back to client
// http://127.0.0.1:8080/logout?success&state=...
// new SimpleUrlLogoutSuccessHandler()...
})
);
return http.build();
}
This assumes validation of some kind is implemented to prevent CSRF, denial of service, etc. You might also choose to add a logout confirmation page as a separate endpoint of the auth server that redirects to the logout endpoint when the user clicks a "Confirm" button, etc.
To use this, you simply redirect from your client application to the logout confirmation page, which redirects to the logout endpoint on confirm, which then redirects back to the client (which can then log out of the client automatically).

How to enrich JWT in spring authorization server?

I have a SAAS server with microservice architecture. Authentication is done by the new Spring authorization server. For some domain situation, I want to be able to re-issue a JWT for a logged-in user without forcing the user to enter their password again to enrich their token with additional claims.
Having: Logged-in user with claim set A.
Required: Create a new token for the user with claim set B. (Without user intervention)
I'm looking for something like this:
#PostMapping("/renew")
public Authentication token() {
return jwtAuthenticationProvider.authenticate(
new BearerTokenAuthenticationToken(JwtUtil.getCurrentAuthenticationTokenValue())
);
}
Where JwtUtil.getCurrentAuthenticationTokenValue() extracts logged-in user token value from SecurityContextHolder. This setup creates no new token and returns the old one like no authentication process has been triggered.
But I cannot find a function/service that generates a new token in spring authorization server.
PS. I cannot use RefreshToken to get new AccessToken because my client is public and according to this, RefreshToken only is issued for confidential clients.
You can read about OAuth2TokenCustomizer in the docs. Here's an example of customizing the access token:
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
In your case, you could issue a new request to the authorization endpoint (e.g. GET /oauth2/authorize?...) from the client to begin the authorization_code flow with different scopes or additional request parameters and use the customizer to add whatever claims you need. Based on the information you've provided, this would be the recommended way to use the authorization server to issue new tokens.
Adding custom endpoints to perform OAuth2-related actions (such as a custom /renew endpoint) without incorporating best practices and standards from the specification(s) would not be recommended.

Spring w/ Cognito get email from SecurityContextHolder

Currently I've got Oauth2 through Cognito set up for my Spring API. I mostly followed this resource and authentication is working so far.
To be able to access a private user's profiles and edit them I want to be able to compare the currently authenticated user with the relevant user of the profile to determine if they're authorized.
My thought on how to do this is that I would define a custom PreAuthorize type that would compare the User from the database's email with the authenticated user's email using the SecurityContextHolder.
However, I'm not able to get the email from the Cognito token. Debugging an endpoint with the below method
var principal = (Jwt)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
principal.getClaims().forEach((k, v) -> System.out.println(k + ": " + v));
None of the values correspond to email. The User Pool is using email to log in, but it creates a generated uid for the username and that's what I receive here.
I could hit the userinfo endpoint manually to try and get the email, but that seems like the wrong way to go about it. Is there a way I can get the user's email to be included in the access token instead of the username? Is there a better way to compare Cognito data to database data?
Here's my relevant code, fairly simple for now:
WebSecurityConfigurerAdapter -
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors();
http.csrf().disable();
http.authorizeRequests(expressionInterceptUrlRegistry -> expressionInterceptUrlRegistry.anyRequest().authenticated())
.oauth2ResourceServer().jwt();
}
Application.yaml -
security:
oauth2:
resourceserver:
jwt:
issuer_uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pKhoaGXXu

How to force an oAuth token renewal (access-token + refresh token) with Spring boot keycloak adapter + spring security.?

I have a multi-tenant application (springboot keycloak adapter + spring security) secured by Keycloak. Given the multi-tenant nature of the project, I wrote a multi-client connector which works fine.
On the official Keycloak doc, it is recommended (for multi-tenant applications) to model each tenant as a new realm, but for me it works better to have multiple clients within the same same realm. This is due to following advantages:
Client scopes, groups and other configs can be shared
Users don't need to be duplicated on N different realms
SSO login works perfectly within same realm clients (by using bearer
services +CORS)
So, everything works fine except for 1 thing, my initial SSO access_token (which is then shared across all bearer-only services by means of CORS) is kind of big (it shows all the resources - tenants - and its roles within each resource/tenant).
I'd like to limit the size of the access_token, by means of using "scopes" to restrict the roles in the token to only those meaningful to the tenant where I'm logged in at that time. For this, I'm manually firing a Request to the auth server (outside of the standard functionality provided by springboot/spring security) with the goal of manually overwriting whatever access-token exists within my app, with the new one generated by my extra request.
My "new" token request looks similar to this:
SimpleKeycloakAccount currentUserAccount = (SimpleKeycloakAccount) auth.getDetails();
String authServerUrl = currentUserAccount.getKeycloakSecurityContext().getDeployment().getAuthServerBaseUrl();
String realm = currentUserAccount.getKeycloakSecurityContext().getDeployment().getRealm();
String resource = currentUserAccount.getKeycloakSecurityContext().getDeployment().getResourceName();
String refreshToken = currentUserAccount.getKeycloakSecurityContext().getRefreshToken();
String token = currentUserAccount.getKeycloakSecurityContext().getTokenString();
Http http = new Http( new Configuration(authServerUrl, realm, resource,
currentUserAccount.getKeycloakSecurityContext().getDeployment().getResourceCredentials()
, null),
(params, headers) -> {});
String url = authServerUrl + "/realms/" + realm + "/protocol/openid-connect/token";
AccessTokenResponse response = http.<AccessTokenResponse>post(url)
.authentication()
.client()
.form()
.param("grant_type", "refresh_token")
.param("refresh_token", refreshToken)
.param("client_id", resource)
.param("client_secret", "SOME_SECRET")
.param("scope", "SOME_SCOPE_TO_RESTRICT_ROLES")
.response()
.json(AccessTokenResponse.class)
.execute();
// :) - response.getToken() and response.getRefreshToken(), contain new successfully generated tokens
My question is, how can I force my-app to change/reset the standard access-token & refresh_token obtained by the usual means, with these "custom created" tokens? or is that possible at all?
Thx for any feedback!
Further Information
To clarify more, lets analyze the behavior of a typical springboot/spring security project integrated with Keycloak:
You protect your endpoints with "roles" via configurations (either on the application.properties, or on the SecurityContext)
You know that this Spring application talks in the back channel with the Keycloak authorization server, that's how you become the access_token (But all this is a black box for the developer, you only know a Principal was created, a Security Context, Credentials; etc - everything happens behind the curtains)
Considering those 2 points above, imagine that you use an Http library to basically request a new token towards the auth server token endpoint like in the code above (yes filtered by scopes and everything). So the situation now is that though you have created a valid access_token (and refresh_token); since they were created "manually" by firing a request towards the token endpoint, this new token hasn't been "incorporated" to the application because No new Principal has been created, no new security context has been generated, etc. In other words, to the springboot application this new token is non-existent.
What I'm trying to accomplish is to tell sprinboot/spring security: "Hey pal, I know you didn't generate this token yourself, but please accept it and behave as if you'd have created it".
I hope this clarifies the intent of my question.
You can revoke a token using org.springframework.security.oauth2.provider.token.ConsumerTokenServices#revokeToken method.
On the Autorization Server:
#Resource(name="tokenServices")
ConsumerTokenServices tokenServices;
#RequestMapping(method = RequestMethod.POST, value = "/tokens/revoke/{tokenId:.*}")
#ResponseBody
public String revokeToken(#PathVariable String tokenId) {
tokenServices.revokeToken(tokenId);
return tokenId;
}
Of course, you'll have to secure this method since is a very sensitive one.
In the case that each tenant is a separate client you can just use keycloak's "Scope" mapping at each client. Just turn off Full Scope Allowed and your tokens will only contain the user's roles for that specific client (tenant).
"Scope Mappings" is a a non intuitive way of saying "Define what roles should go into the access token" :-)
When turned off the UI changes and you even can configure what other roles of other clients should additionally go into the access token.
Just to give some closure to this question:
No, there doesn't seem to be any elegant or intended way to force a manual token renewal by means of using springboot/spring security keycloak connector.
The Javascript connector can do this trivially like this:
// for creating your keycloak connector
var keycloak = Keycloak({
url: 'http://localhost:8080/auth',
realm: '[YOUR_REALM]',
clientId: '[YOUR_CLIENT]'
});
// for login in (change scopes list to change access capabilities)
var options = {
scope: [EMPTY_STRING_SEPARATED_LIST_OF_SCOPES] // <-- here specify valid scopes
};
keycloak.login(options); // <-- receive a new token with correctly processed scopes
Given how easy it is to do this with the Keycloak client JS adapter, and how obscure it is to do this with the springboot/spring security adapter, it follows following:
Security design seems intended to have 2 (Keycloak security) layers; the first is a front-facing public client (usually password protected), and the 2nd layer is composed of several bearer-only services which would ussually only accept acces-tokens. If for those bearer-only services you want to implement finner grained control via scopes, you achieve that trivially by using a javascript based Keycloak client (other connectors as explained won't deal nicely with the header modification necessary to deal with OAuth2 scopes).

Spring MVC based application is not landing to default success url while trying to access it using https url but working fine on http based url

While running the spring mvc based application on http, it is working fine but once the application is moved to https based configuration, we are not able to get successful landing url page. The url which are non authenticated is working fine but the authenticated urls are not reachable even after providing correct login credentials.
Please help me with the missing configurations/issues to resolve this problem.
The application is deployed over Jboss server.
Below are the configurations done as part of application.
protected void configure(HttpSecurity http) throws Exception {
System.out.println("I am configure");
http.csrf().disable();
// The pages does not require login
http.authorizeRequests().antMatchers("/login", "/logout", "/resetPassword", "/forgotPassword", "/change-password", "/confirm-account", "/registerPage", "/registerwithemail", "/resources/**", "/logoutSuccessful", "/login/**").permitAll();
http.authorizeRequests().antMatchers("/**").authenticated();
http.authorizeRequests().antMatchers("/home").authenticated();
http.authorizeRequests().antMatchers("/").authenticated();
// /userInfo page requires login as USER or ADMIN.
// If no login, it will redirect to /login page.
http.authorizeRequests().antMatchers("/userInfo").access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");
// For ADMIN only.
http.authorizeRequests().antMatchers("/admin").access("hasRole('ROLE_ADMIN')");
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will throw.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");
http.headers().frameOptions().sameOrigin();
// Config for Login Form
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginPage("/login")//
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.defaultSuccessUrl("/home", true)// userInfo (Changed to) home
.failureUrl("/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
// For Remember-me
.and().rememberMe().rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400)
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/logoutSuccessful").invalidateHttpSession(true).deleteCookies("JSESSIONID", "remember-me");
}
if there is any channel security for http/https is required, please let me know about the configurations as I'm also traversing internally to different module using ip and port based http url.

Resources