Spring Security oauth и JWT блокирует все запросы - PullRequest
0 голосов
/ 24 мая 2018

Я попытался настроить простое приложение, которое авторизует действия в службе REST на основе базовых заголовков авторизации, содержащих JWT.Я использовал автоматически предоставленную конечную точку oauth / token, чтобы клиенты могли получить JWT от имени действительного пользователя.К сожалению, Spring Security блокирует все запросы, поэтому я сделал что-то не так в своей конфигурации, и я надеялся, что кто-то сможет увидеть, что это было.

Это моя конфигурация:

Источник данных (где пользователии клиент настроен:

@Configuration
public class DatasourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource datasource(Environment env){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(env.getRequiredProperty("url"));
        dataSource.setUsername(env.getRequiredProperty("username"));
        dataSource.setPassword(env.getRequiredProperty("password"));
        return dataSource;
    }
}

Конфигурация безопасности:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Autowired
    @Qualifier("appUserDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private DataSource datasource;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder =
                PasswordEncoderFactories.createDelegatingPasswordEncoder();
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic()
                .realmName(securityRealm)
                .and()
                .csrf()
                .disable();

    }


    @Bean
    public JwtAccessTokenConverter jwtTokenEnhancer() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }


    @Bean
    public JdbcTokenStore tokenStore() {
        return new JdbcTokenStore(datasource);
    }


    @Bean
    @Autowired
    public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
        TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
        handler.setTokenStore(tokenStore);
        handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
        handler.setClientDetailsService(clientDetailsService);
        return handler;
    }

    @Bean
    @Autowired
    public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
        TokenApprovalStore store = new TokenApprovalStore();
        store.setTokenStore(tokenStore);
        return store;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

Сервер аутентификации:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private static String REALM="AVMAINT_REALM";

    @Autowired
    @Qualifier("appUserDetailsService")
    private UserDetailsService userDetailsService;

    @Autowired
    private UserApprovalHandler userApprovalHandler;

    @Autowired
    private DataSource datasource;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtTokenEnhancer;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
      clients.jdbc(datasource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore).tokenEnhancer(jwtTokenEnhancer).userApprovalHandler(userApprovalHandler)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.realm(REALM);
    }
}

и отделяя мои пути от моей авторизации, у меня естьсервер ресурсов

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


    @Value("${security.jwt.resource-ids}")
    private String resourceIds;


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()
                .and()
                .authorizeRequests()
                .antMatchers("/actuator/**", "/api-docs/**", "/oauth/token", "/auth/login").permitAll()
                .antMatchers("/api/**" ).authenticated()
                .and()
                    .formLogin()
                        .successHandler(successHandler())
                        .failureHandler(failureHandler())
                .and()
                    .exceptionHandling()
                        .accessDeniedHandler(accessDeniedHandler())
                        .authenticationEntryPoint(authenticationEntryPoint())

        ;
    }

    private AuthenticationSuccessHandler successHandler() {
        return new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                httpServletResponse.getWriter().append("OK");
                httpServletResponse.setStatus(200);
            }
        };
    }

    private AuthenticationFailureHandler failureHandler() {
        return new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                httpServletResponse.getWriter().append("Authentication failure");
                httpServletResponse.setStatus(401);
            }
        };
    }

    private AccessDeniedHandler accessDeniedHandler() {
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
                httpServletResponse.getWriter().append("Access denied");
                httpServletResponse.setStatus(403);
            }
        };
    }


    private AuthenticationEntryPoint authenticationEntryPoint() {
        return new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                httpServletResponse.getWriter().append("Not authenticated");
                httpServletResponse.setStatus(401);
            }
        };
    }
}

Это настройка данных:

CREATE TABLE user (
  id bigint NOT NULL AUTO_INCREMENT,
  username varchar(64) NOT NULL,
  password varchar(64) NOT NULL,
  organisation_id bigint,
  person_id bigint,
  PRIMARY KEY (id),
  CONSTRAINT fk_user_organisation FOREIGN KEY (organisation_id) REFERENCES organisation(id),
  CONSTRAINT fk_user_person FOREIGN KEY (person_id) REFERENCES person(id)
);

CREATE TABLE role (
  id bigint NOT NULL AUTO_INCREMENT,
  description varchar(255) DEFAULT NULL,
  role_name varchar(255) DEFAULT NULL,
  PRIMARY KEY (id)
);

CREATE TABLE user_role (
  user_id bigint,
  role_id bigint,
  CONSTRAINT fk_user_role_user FOREIGN KEY (user_id) REFERENCES user(id),
  CONSTRAINT fk_user_role_role FOREIGN KEY (role_id) REFERENCES role(id)
);

INSERT INTO role (id, role_name, description) VALUES (1, 'Root User - Has permission to perform admin tasks', 'ROOT_USER');
INSERT INTO role (id, role_name, description) VALUES (2, 'Admin User - Has permission to admin organisation', 'ADMIN_USER');
INSERT INTO role (id, role_name, description) VALUES (3, 'Standard User - Has no admin rights', 'STANDARD_USER');

-- USER
-- non-encrypted password: jwtpass
INSERT INTO user (id, password, username) VALUES (1, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'xxxxxxxx@xxxxxxx.com.au');
INSERT INTO user (id, password, username) VALUES (2, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'frank@frankthring.org');
INSERT INTO user (id, password, username) VALUES (3, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'mike@frankthring.org');
INSERT INTO user (id, password, username) VALUES (4, '$2y$12$AyfKqP6YvubgFVHp0AGzs.VmrDaoIja3rUWncFkpLBSERGqAY94Vm', 'xxxxxxx@xxxxxx.com');

INSERT INTO user_role(user_id, role_id) VALUES(1,1);
INSERT INTO user_role(user_id, role_id) VALUES(2,2);
INSERT INTO user_role(user_id, role_id) VALUES(3,3);
INSERT INTO user_role(user_id, role_id) VALUES(4,1);

CREATE TABLE oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

INSERT INTO oauth_client_details
(client_id, client_secret, scope, authorized_grant_types,
 authorities, access_token_validity, refresh_token_validity)
VALUES
  ('avmaintwebsitejwtclientid', '$2y$12$pJ6DTB2Qu9FgK4V9ai38Z.N49faYX04HqO6K/Jz7eeE8r5BjrIZOe', 'read,write,trust', 'password,refresh_token',
   'ROLE_CLIENT,ROLE_TRUSTED_CLIENT', 900, 2592000);

Наконец, вот функциональный тест, который показывает, что даже конечная точка / oauth / token заблокирована:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@TestPropertySource("/application-embedded.yml")
@AutoConfigureMockMvc
public class AccessControllerFunctionalTest {

    @Autowired
    private WebApplicationContext context;

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .build();
    }

    @Test
    public void doAuthorization() throws Exception {

        // I need this for the moment, because it looks like the context isn't loading properly.
        when(userRepository.findByUsername(eq("xxxxxxx@gmail.com"))).thenReturn(User.of(
                "rmjcoxon@gmail.com",
                passwordEncoder.encode("password")));

        String requestJson = new ObjectMapper().writeValueAsString(User.of(
                "xxxxxxx@gmail.com",
                "password"));

        String requestString = "username=xxxxxxxx@gmail.com&password=" + passwordEncoder.encode("password") + "&grant_type=password";

        mvc.perform(post("/oauth/token").contentType(APPLICATION_FORM_URLENCODED)
                .content(requestString)
                .with(httpBasic("avmaintwebsitejwtclientid",passwordEncoder.encode("XY7kmzoNzl100"))))
                .andDo(print()).andExpect(status().isOk())
                .andExpect(content().string(containsString("{\"payload\":\"ok\",\"code\":\"OK\"}")));
    }

}

Это ошибка, которую я получаю почти для всего:

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /oauth/token
       Parameters = {username=[rmjcoxon@gmail.com], password=[$2a$10$BcQ6RzxSCKjeWWKJDFmUJ.Dj8l6pv7BKu5eXDt5lq/SklUcwS7XfK], grant_type=[password]}
          Headers = {Content-Type=[application/x-www-form-urlencoded], Authorization=[Basic YXZtYWludHdlYnNpdGVqd3RjbGllbnRpZDokMmEkMTAkWEFqVzJVUUhUckh4RWlUT0M1RFRndUQ4L2I3UXlFQURuWUdUaFF5dUdqWU9FREFFcVhQb0s=]}
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = null

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = Unauthorized
          Headers = {WWW-Authenticate=[Basic realm="AVMAINT_REALM"], X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2018-05-24 19:18:54.640  INFO 15780 --- [      Thread-10] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@45bf9982: startup date [Thu May 24 19:18:46 AEST 2018]; root of context hierarchy
2018-05-24 19:18:54.643  INFO 15780 --- [      Thread-10] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
au.com.avmaint.api.access.AccessControllerFunctionalTest > doAuthorization FAILED

Status expected:<200> but was:<401>
Expected :200
Actual   :401
 <Click to see difference>

java.lang.AssertionError: Status expected:<200> but was:<401>
    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:55)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:82)
    at org.springframework.test.web.servlet.result.StatusResultMatchers.lambda$matcher$9(StatusResultMatchers.java:617)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:178)
    at au.com.avmaint.api.access.AccessControllerFunctionalTest.doAuthorization(AccessControllerFunctionalTest.java:80)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Кто-нибудь может увидеть, что я сделал неправильно?

...