Comments (5)
Thank you for detailed report, this will save me quite some time ;)
You are running with Java 1.8.0_241
when main README states a minimum of 11 is required.
A few side notes on your code snippets. You could remove at minimum spring-security-oauth2-addons
and spring-security-oauth2-test-webflux-addons
dependencies. Then depending your are using the functionalities from MockMvcSupport
or not, keep simply spring-security-oauth2-test-webmvc-addons
(the more addons) or spring-security-oauth2-test-addons
(the closer to spring-security-test).
MockMvcSupport
is, as explained in the javadoc, just a wrapper around Spring's MockMvc
I designed to:
- set default
Accept
andContent-type
headers if not provided - choose a serializer found in project config according to the headers (so that you get same payloads as when running for real)
- modify a bit the flow and shorten it for most frequent cases
mockMvc.with(...).get(...).andExpect(...)
instead ofmockMvc.perform(get(...).with(...)).andExpect(...)
Last, you didn't @MockBean JwtDecoder jwtDecoder;
. This might cause your resource-server trying to contact configured authorization-server at startup (and throw a SocketException if it can't reach it).
from spring-addons.
Thank you so much for your time and for such a detailed explanation. The below tests are passing now:
@WebMvcTest(controllers = TestController.class)
@Import({
ServletKeycloakAuthUnitTestingSupport.class,
KeycloakSecurityConfig.class })
class TestControllerTest {
@MockBean
JwtDecoder jwtDecoder;
@Autowired
ServletKeycloakAuthUnitTestingSupport keycloak;
@Autowired
private MockMvcSupport mockMvc;
@Test
public void shouldBeAccessedWithoutToken() throws Exception {
this.mockMvc
.get("/test/anonymous").andExpect(status().isOk());
}
@Test
public void shouldNotBeAccessedWithoutToken() throws Exception {
this.mockMvc
.get("/test/admin").andExpect(status().isUnauthorized());
}
...
The last question is how to test the secured end-point providing a token in the header?
I tried as follows:
@Test
public void shouldBeAccessedWithValidToken() throws Exception {
this.mockMvc
.with(keycloak.keycloakAuthenticationToken())
.get("/test/admin").andExpect(status().isUnauthorized());
}
but it failed with:
java.lang.AssertionError: Status expected:<401> but was:<403>
Expected :401
Actual :403
I see that there is no token present in the request header ...
I have the Keycloak config class defined as follows:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/test/anonymous").permitAll()
.antMatchers("/test/user").hasAnyRole("user")
.antMatchers("/test/admin").hasAnyRole("admin")
.antMatchers("/test/all-user").hasAnyRole("user","admin")
.anyRequest()
.permitAll();
http.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());
}
@Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
I also tried to mimic your example and modified the previous test:
package com.example.controllers;
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.MockAuthenticationRequestPostProcessor;
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.MockMvcSupport;
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.keycloak.ServletKeycloakAuthUnitTestingSupport;
import com.example.config.KeycloakSecurityConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import static com.c4_soft.springaddons.security.oauth2.test.mockmvc.MockAuthenticationRequestPostProcessor.mockAuthentication;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(controllers = TestController.class)
@Import({
ServletKeycloakAuthUnitTestingSupport.class,
KeycloakSecurityConfig.class })
class TestControllerTest {
@MockBean
JwtDecoder jwtDecoder;
@Autowired
ServletKeycloakAuthUnitTestingSupport keycloak;
@Autowired
private MockMvcSupport mockMvc;
@MockBean
JwtAuthenticationConverter authenticationConverter;
@Test
public void shouldBeAccessedWithoutToken() throws Exception {
this.mockMvc
.get("/test/anonymous").andExpect(status().isOk());
}
@Test
public void shouldNotBeAccessedWithoutToken() throws Exception {
this.mockMvc
.get("/test/admin").andExpect(status().isUnauthorized());
}
@Test
public void shouldBeAccessedWithValidToken() throws Exception {
this.mockMvc
.with(adminEmployee())
.get("/test/admin").andExpect(status().isOk());
}
private MockAuthenticationRequestPostProcessor<JwtAuthenticationToken> adminEmployee() {
return mockAuthentication(JwtAuthenticationToken.class).name("employee2").authorities("app-admin");
}
interface JwtAuthenticationConverter extends Converter<Jwt, JwtAuthenticationToken> {
}
}
but still getting
java.lang.AssertionError: Status expected:<200> but was:<403>
Expected :200
Actual :403
<Click to see difference>
for the last test 😞
I can't see how I can set a client_id, a specific client role, a user, etc...
from spring-addons.
Even like this, it fails:
@Test
@WithMockKeycloakAuth(
accessToken = @KeycloakAccessToken(
realmAccess = @KeycloakAccess(roles = { "app-admin" })
)
)
public void whenAdminRolesSpecifiedThenSecuredRouteIsOk() throws Exception {
mockMvc.get("/test/admin").andExpect(status().isOk());
}
from spring-addons.
Decorating your test with @WithMockKeycloakAuth("admin")
should be enough for access to /test/admin
to be granted.
You obviously lack quite some background
First about HTTP statuses:
- 401 (unauthorized): no authentication
- 403 (forbiden): authenticated but access denied (security rule broken due to authorities contained in
Authentication
instance, most probably)
Second, neither JwtAuthenticationToken
nor KeycloakAuthenticationToken
are tokens. Both are Authentication
implementation provided by respectively spring-security and Keycloak libs. Providing a mocked JwtAuthenticationToken
when testing an app configured to use KeycloakAuthenticationToken
is strange but the reason for your last failure is your test is Authentication
contains "app-admin" authority when .antMatchers("/test/admin").hasAnyRole("admin")
(ROLE_admin
is expected, not app-admin
) => either use .authorities("ROLE_admin")
int he test or .hasAnyAuthority("app-admin")
in security conf (note hasAnyAuthority
instead of hasAnyRole
which adds a "ROLE_" prefix)
Third, as I already wrote, the point of this lib is not to create a valid token, put it int the Authorization header, have it processed by the all decoding pile and finally turned into a KeycloakAuthenticationToken
. You won't find JWTs anywhere in the requests.
The point is to directly feed the SecurityContext
with an Authentication
of type KeycloakAuthenticationToken
as if a valid JWT issued by Keycloak was successfully decoded, validated and turned into a KeycloakAuthenticationToken
(what keycloak-spring-boot-starter
does at runtime).
If you are not sure about what precisely SecurityContext
, Authentication
and GrantedAuthority
are, you should urgently open spring-security doc.
Last, how you should build your KeycloakAuthenticationToken
mostly depends on how you configured your realm, client and users on Keycloak one the one hand, and spring-security on your resource-server(s) on the other. Two things you should do:
- open keycloak doc
- set breakpoint at a place like this one (where a
KeycloakAuthenticationToken
is injected) and debug using your real Keycloak. That way, you should see what exactly theKeycloakAuthenticationToken
look like when built by keycloak lib from valid tokens and then mimic the claims structure (you just need to clone my repo, editapplication.properties
to point to your Keycloak and right clickKeycloakSpringBootSampleApp.java
=> "debug as" => "spring-boot app").
Important note
You don't have to set "roles" claims and wire an authorities mapper in test conf as I provide with an authorities
properties in @WithMockKeycloakAuth
which will populate granted authorities.
from spring-addons.
Thank you once again for the explained points.
Frankly, it would be much easier to figure out what annotation to use in which case, what to mock and what not if, and only if, everybody puts some efforts into writing good documentation, - this would keep me from browsing your code source, examples (often disconnected from a real context, etc.). There are many other open-sourced libraries, add-ons, plugins, etc. but the way it goes in Java world is especially frustrating and strikes the eye.
I'll take a try and follow your guidelines. As it is >= jdk 11 only, actually it does not suit our team.
Thank you.
from spring-addons.
Related Issues (20)
- `authorization-request-params` ignored HOT 1
- POST /logout response Forbidden 403 HOT 9
- Support several JWT authentication converters (or converters with a `@Qualifier` which is not `jwtAuthenticationConverter`)
- Doubled path-prefix by `SpringAddonsServerOAuth2AuthorizationRequestResolver` HOT 1
- Allow anonymous CORS preflight requests (`OPTIONS` requests to a path configured with CORS) HOT 1
- Configuration properties to add parameters to token requests HOT 1
- Spring Starter OICD, Resource Server: Option to disable the default behavior for authorized/protected routes HOT 1
- BFF configuration token is not refreshed HOT 3
- Getting response 401 (Unauthorized) for permit-all requests after update HOT 2
- (Not a bug)Why the custom JwtDecoder bean is useless HOT 2
- `spring-security-oauth2-resource-server`, `spring-security-oauth2-client` and `spring-webflux` should be `optional` dependencies HOT 1
- Support for resource owner password credential flow (ROPC) HOT 1
- Handle CORS Requests with Keycloak's "allowed-origins" claim like the keycloak adapter (now deprecated) HOT 2
- Downstream services times out reading request body when csrf is set to cookie-accessible-from-js HOT 2
- Expand servlet-client tutorial to show calling servlet-resource-server with user that has NICE privileges. HOT 2
- Logout Issue (Invalid CSRF Token) HOT 3
- Import keycloak realms with spring-addons-starters-rest HOT 1
- `@WithOidcLogin` using json file similarly as `@WithJwt` HOT 8
- Need support in resolving 401 Unauthorized Error for Multi Tenant JWT Auth with Resource Server HOT 1
- Invalid SpringAddonsOidcProperties breaks native image HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from spring-addons.