【问题标题】:Spring boot Oauth2 - Security incompatibility between Authorization and Resource serverSpring boot Oauth2 - 授权和资源服务器之间的安全不兼容
【发布时间】:2018-11-24 13:05:34
【问题描述】:

我正在尝试使用 spring 构建一个 Oauth2 授权服务器。问题是我无法使它与登录和授权表单以及资源服务器一起使用 oauth2 令牌检索用户数据。

这是我除了用户服务和存储库之外的主要配置...

网络安全配置

@EnableWebSecurity
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource(name = "userService")
    private UserDetailsService userDetailsService;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(encoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login", "/oauth/authorize").permitAll()
                .and()
                .formLogin().permitAll();
    }

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

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

    @Bean
    public BCryptPasswordEncoder encoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public FilterRegistrationBean corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private Environment env;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private UserApprovalHandler userApprovalHandler;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    @Qualifier("dataSource")
    private DataSource dataSource;

    @Value("classpath:schema.sql")
    private Resource schemaScript;

    @Value("classpath:data.sql")
    private Resource dataScript;

    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
        configurer.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .tokenStore(tokenStore)
                .userApprovalHandler(userApprovalHandler)
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) {
        oauthServer
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    @Bean
    public DataSourceInitializer dataSourceInitializer() {
        final DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource);
        initializer.setDatabasePopulator(databasePopulator());
        return initializer;
    }

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

    @Bean
    public ClientCredentialsTokenEndpointFilter checkTokenEndpointFilter() {
        ClientCredentialsTokenEndpointFilter filter = new ClientCredentialsTokenEndpointFilter("/oauth/check_token");
        filter.setAuthenticationManager(authenticationManager);
        filter.setAllowOnlyPost(true);
        return filter;
    }


    private DatabasePopulator databasePopulator() {
        final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(schemaScript);
        populator.addScript(dataScript);
        return populator;
    }

    private DataSource dataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.password"));
        return dataSource;
    }
}

资源服务器配置

@Configuration
@EnableResourceServer
@Order(3)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "resource_id";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(RESOURCE_ID).stateless(false);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user").authenticated()
                .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
    }

}

用户控制器

@Controller
public class UserController {

    @Autowired
    UserService userService;

    @PreAuthorize("#oauth2.hasScope('read_user_profile')")
    @GetMapping("/user")
    @ResponseBody
    public Optional<User> getUser(@RequestParam String email) {
        return userService.findAll().stream().filter(x -> x.getEmail().equals(email)).findAny();
    }

    @PostMapping(value = "/user", consumes = MediaType.APPLICATION_JSON_VALUE)
    @ResponseStatus(HttpStatus.CREATED)
    public void postMessage(@RequestBody User user) {
        userService.save(user);
    }

}

如您所见,我为他们分配了订单。问题是,如果 WebSecurityConfig 是第一个,我可以进入 /login 和 /oauth/authorize 屏幕,但用户控制器没有任何安全层并且它是开放的(不需要令牌):S

如果顺序相反,我看不到登录页面,但我看到了 404。顺便说一下,它从 /oauth/authorize 重定向。但我可以使用生成的令牌访问用户控制器。

我做错了什么?不能在同一个模块中吗?

【问题讨论】:

    标签: java spring-boot oauth-2.0 spring-security-oauth2


    【解决方案1】:

    我认为这不是“订单”问题。

    @PreAuthorize 注解被方法安全使用。 如果想让它工作,需要@EnableGlobalMethodSecurity(prePostEnabled=true)注解。

    但是如果你只是想通过 OAuth 保护你的资源,为什么不在 ResourceServerConfig 中使用基于 url 的安全约束进行配置呢?

    例如,我的 ResourceServerConfig 工作正常,如下所示:

        @Override
        public void configure(HttpSecurity http) throws Exception {
             http.antMatcher("/api/**")
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/user").access("#oauth2.hasScope('read')");
        }
    

    【讨论】:

    • 因为那行不通。好像没有考虑资源服务器的安全配置。
    • 你试过我的建议了吗?我向您展示了 2 个解决方案。我的代码没有问题并且工作完美。如果您的代码不起作用,则说明缺少某些部分或部分错误。
    • 是的,它可以使用您的 adivce 但不能使用注释。除此之外,我的主要问题是让两种配置一起工作。如果我更改其中一个的顺序,我可以使用令牌获取资源,但不能按照隐式流程的形式登录。而反过来。你对此有什么建议吗? @Jes
    • 我提到了@EnableGlobalMethodSecurity(prePostEnabled=true) 注释。不行吗?
    • 它可以通过定义 MethodSecurityConfig 来工作。但这与在设置 Oauth2 范围的方法中使用注释有关,这与我的问题无关,即如果您定义了全局安全配置,则资源安全配置将被忽略。如果您更改顺序,则忽略另一个顺序。 github.com/spring-projects/spring-security-oauth/issues/1024
    猜你喜欢
    • 2018-08-29
    • 2015-05-14
    • 2016-05-21
    • 2014-07-09
    • 2022-08-03
    • 2019-04-05
    • 2016-04-22
    • 2017-08-26
    • 2019-10-27
    相关资源
    最近更新 更多