Coder Social home page Coder Social logo

Comments (5)

ch4mpy avatar ch4mpy commented on June 13, 2024

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 and Content-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 of mockMvc.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.

belgoros avatar belgoros commented on June 13, 2024

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.

belgoros avatar belgoros commented on June 13, 2024

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.

ch4mpy avatar ch4mpy commented on June 13, 2024

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 the KeycloakAuthenticationToken look like when built by keycloak lib from valid tokens and then mimic the claims structure (you just need to clone my repo, edit application.properties to point to your Keycloak and right click KeycloakSpringBootSampleApp.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.

belgoros avatar belgoros commented on June 13, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.