【问题标题】:Spring OAuth security - Implicit flowSpring OAuth 安全性 - 隐式流
【发布时间】:2019-07-26 15:44:46
【问题描述】:

是否可以使用 Spring Security 实现 OAuth 隐式流?我想在同一个应用程序中创建身份验证和资源服务器。我需要标准的身份验证端点来进行身份验证和授权,还需要一些自定义端点来处理用户(创建/更新/列表...)。

要求:

  • 隐式流
  • 自定义登录页面 (/my_login_page)
  • 获取令牌的静默模式 (/oauth/authorize?...&prompt=none)
  • 使用 OAuth (/users) 保护自定义端点

我被配置卡住了。无论我做什么,上述要求永远不会一起工作。

春天WebSecurityConfig

@Configuration
@Order(-10)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private MyAuthenticationProvider authenticationProvider;
    private MyAuthenticationDetailsSource authenticationDetailsSource;

    @Autowired
    public SecurityConfig(MyAuthenticationProvider authenticationProvider, MyAuthenticationDetailsSource authenticationDetailsSource) {
        this.authenticationProvider = authenticationProvider;
        this.authenticationDetailsSource = authenticationDetailsSource;
    }

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .authenticationProvider(authenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http

            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.NEVER)
            .sessionFixation().newSession()
            .and()

            .authorizeRequests()
            .antMatchers("/assets/**", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/v2/**").permitAll()
            .anyRequest().authenticated()
            .and()

            .formLogin()
            .loginPage("/my_login_page")
            .loginProcessingUrl("/my_process_login")
            .usernameParameter("my_username")
            .passwordParameter("pmy_assword")
            .authenticationDetailsSource(authenticationDetailsSource)
            .permitAll();
    }
}

春天AuthorizationServerConfig

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private ResourceLoader resourceLoader;
    private AuthProps authProps;

    @Autowired
    public OAuth2AuthorizationServerConfig(ResourceLoader resourceLoader, AuthProps authProps) {
        this.resourceLoader = resourceLoader;
        this.authProps = authProps;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    @Qualifier("jwtAccessTokenConverter")
    public JwtAccessTokenConverter accessTokenConverter() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resourceLoader.getResource(authProps.getAuthServerPrivateCertPath()), authProps.getAuthServerPrivateCertKey().toCharArray());
        JwtAccessTokenConverter converter = new MYJwtAccessTokenConverter();   
        converter.setKeyPair(keyStoreKeyFactory
            .getKeyPair(authProps.getAuthServerPrivateCertAlias()));

        final Resource resource = resourceLoader.getResource(authProps.getAuthServerPublicCertPath());
        String publicKey;
        try {
            publicKey = IOUtils.toString(resource.getInputStream());
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
        converter.setVerifierKey(publicKey);

        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }

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

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("my-secured-client")
            .secret("foo")
            .authorizedGrantTypes("implicit")
            .scopes("read", "write")
            .resourceIds("my-resource")
            .authorities("CLIENT")
            .redirectUris(
                    "http://localhost:4200"
            )
            .accessTokenValiditySeconds(300)
            .autoApprove(true);
    }
}

春天ResourceServerConfig

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private AuthProps authProps;
    private TokenStore tokenStore;
    private DefaultTokenServices tokenServices;

    @Autowired
    public OAuth2ResourceServerConfig(AuthProps authProps, TokenStore tokenStore, DefaultTokenServices tokenServices) {
        this.authProps = authProps;
        this.tokenStore = tokenStore;
        this.tokenServices = tokenServices;
    }

    @Override
    public void configure(final ResourceServerSecurityConfigurer config) {
        config
            .resourceId("my-resource")
                .tokenStore(tokenStore)
                .tokenServices(tokenServices);
    }

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS).permitAll()
            .antMatchers("/**").authenticated()
            .and()
            .csrf().disable();
    }
}

我将WebSecurityConfig 放在ResourceServerConfig 之前,否则登录页面不起作用。但现在我无法访问我的用户自定义端点(我被重定向到登录页面)。如果我将ResourceServerConfig 放在WebSecurityConfig 之前,登录页面将停止工作。提交登录页面表单时收到 404 not found 响应。

我在静默模式下获取新的访问令牌时也遇到了问题。当使用仍然有效的access_token 调用/oauth/authorize 时,我被重定向到登录页面。

【问题讨论】:

    标签: spring spring-security spring-security-oauth2


    【解决方案1】:

    除了@user3714967 的回答,我添加了一些提示,也许它可以帮助某人。问题是我们定义了多个HttpSecurity(resourceServer 是一个 WebSecurityConfigurerAdapter,顺序为 3)。解决方法是使用HttpSecurity.requestMatchers(),具体值。

    示例

    头等舱:

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.requestMatchers().antMatchers("url1", "url2", ...).and()
                .authorizeRequests()
                    .antMatchers(...).and()...
        }
    }
    

    二等:

    @Configuration
    @EnableResourceServer
    public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
       @Override
        public void configure(HttpSecurity http) throws Exception {
             @Override
             protected void configure(HttpSecurity http) throws Exception {
                 http
                   .requestMatchers().antMatchers("url3", "url4", ...)
                    .and()
                     .authorizeRequests()
                        .antMatchers(...).and()...
        }
       }
     }
    

    当我们有更多的流时,这将很有用(密码 && 隐式流对于我的情况)。

    【讨论】:

      【解决方案2】:

      终于找到了解决办法:

      1. ResourceServerConfig 必须在 WebSecurityConfig 之前
      2. loginProcessingUrl 应该是 /oauth/authorize
      3. 默认情况下静默刷新工作,直到会话有效(登录表单)
      4. 用于注销当前会话无效的自定义端点

      已编辑:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
      
          private MyAuthenticationProvider authenticationProvider;
          private MyAuthenticationDetailsSource authenticationDetailsSource;
      
          @Autowired
          public SecurityConfig(MyAuthenticationProvider authenticationProvider, MyAuthenticationDetailsSource authenticationDetailsSource) {
              this.authenticationProvider = authenticationProvider;
              this.authenticationDetailsSource = authenticationDetailsSource;
          }
      
          @Bean
          @Override
          public AuthenticationManager authenticationManagerBean() throws Exception {
              return super.authenticationManagerBean();
          }
      
          @Override
          protected void configure(AuthenticationManagerBuilder auth) {
              auth
                      .authenticationProvider(authenticationProvider);
          }
      
          @Override
          public void configure(WebSecurity web) {
              web
                      .debug(true)
                      .ignoring()
                      .antMatchers(HttpMethod.OPTIONS)
                      .antMatchers("/my-custom-login-page", "/my-custom-logout-page")
                      .antMatchers("/assets/**", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/v2/**");
          }
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                      .and()
      
                      .authorizeRequests()
                      .anyRequest().authenticated()
                      .and()
      
                      .formLogin()
                      .loginPage("/my-custom-login-page")
                      .loginProcessingUrl("/oauth/authorize")
                      .usernameParameter("myUsernameParam")
                      .passwordParameter("myPasswordParam")
                      .authenticationDetailsSource(authenticationDetailsSource)
                      .permitAll()
                      .and()
      
                      .csrf().disable();
          }
      }
      
      
      @Configuration
      @EnableAuthorizationServer
      public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
      
          private ResourceLoader resourceLoader;
          private AuthProps authProps;
      
          @Autowired
          public OAuth2AuthorizationServerConfig(ResourceLoader resourceLoader, AuthProps authProps) {
              this.resourceLoader = resourceLoader;
              this.authProps = authProps;
          }
      
          @Bean
          public TokenStore tokenStore() {
              return new JwtTokenStore(accessTokenConverter());
          }
      
          @Bean
          @Qualifier("jwtAccessTokenConverter")
          public JwtAccessTokenConverter accessTokenConverter() {
              KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resourceLoader.getResource(authProps.getAuthServerPrivateCertPath()), authProps.getAuthServerPrivateCertKey().toCharArray());
              JwtAccessTokenConverter converter = new MyJwtAccessTokenConverter();
              converter.setKeyPair(keyStoreKeyFactory.getKeyPair(authProps.getAuthServerPrivateCertAlias()));
      
              final Resource resource = resourceLoader.getResource(authProps.getAuthServerPublicCertPath());
              String publicKey;
              try {
                  publicKey = IOUtils.toString(resource.getInputStream());
              } catch (final IOException e) {
                  throw new RuntimeException(e);
              }
              converter.setVerifierKey(publicKey);
      
              return converter;
          }
      
          @Bean
          @Primary
          public DefaultTokenServices tokenServices() {
              DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
              defaultTokenServices.setTokenStore(tokenStore());
              return defaultTokenServices;
          }
      
          @Override
          public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
              endpoints
                      .tokenStore(tokenStore())
                      .accessTokenConverter(accessTokenConverter());
          }
      
          @Override
          public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
              oauthServer
                      .tokenKeyAccess("permitAll()")
                      .checkTokenAccess("isAuthenticated()");
          }
      
          @Override
          public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
              clients.inMemory()
                      .withClient(authProps.getAuthServerClientId())
                      .secret(authProps.getAuthServerClientSecret())
                      .authorizedGrantTypes("implicit")
                      .scopes("read", "write")
                      .resourceIds(authProps.getAuthServerResourceId())
                      .authorities("CLIENT")
                      .redirectUris(
                              "http://localhost:4200/#/login",
                              "http://localhost:4200/assets/silent-refresh.html",
                              "http://localhost:8080/my-api/webjars/springfox-swagger-ui/oauth2-redirect.html"
                      )
                      .accessTokenValiditySeconds(authProps.getAuthServerAccessTokenValiditySeconds())
                      .autoApprove(true);
          }
      }
      
      
      @Configuration
      @EnableResourceServer
      public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
      
          private AuthProps authProps;
          private TokenStore tokenStore;
          private DefaultTokenServices tokenServices;
      
          @Autowired
          public OAuth2ResourceServerConfig(AuthProps authProps, TokenStore tokenStore, DefaultTokenServices tokenServices) {
              this.authProps = authProps;
              this.tokenStore = tokenStore;
              this.tokenServices = tokenServices;
          }
      
          @Override
          public void configure(final ResourceServerSecurityConfigurer config) {
              config.resourceId(authProps.getAuthServerResourceId()).tokenStore(tokenStore);
              config.resourceId(authProps.getAuthServerResourceId()).tokenServices(tokenServices);
          }
      
          @Override
          public void configure(final HttpSecurity http) throws Exception {
              http
                      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                      .and()
      
                      .authorizeRequests()
                      .anyRequest().hasRole(AppRole.ROLE_APP_USER.split("ROLE_")[1])
                      .and()
      
                      .csrf().disable();
          }
      }
      
      
      @Controller
      public class MainController {
      
          @Autowired
          public MainController() {
              ...
          }
      
          @GetMapping("/my-custom-login-page")
          public ModelAndView loginPage(HttpServletRequest request, HttpServletResponse response) {
              ModelAndView mv = new ModelAndView("login-page");
              return mv;
          }
      
          @GetMapping("/my-custom-logout-page")
          public ModelAndView logoutPage(HttpServletRequest request) {
              ModelAndView mv = new ModelAndView("logout-page");
      
              HttpSession session = request.getSession(false);
              if (Objects.isNull(session)) {
                  mv.addObject("msg", "NO SESSION");
                  return mv;
              }
              session.invalidate();
              mv.addObject("msg", "SUCCEEDED");
              return mv;
          }
      }
      
      

      【讨论】:

      • 我有同样的问题,你的解决方案对我不起作用。仅当我将 WebSecurityConfig 的顺序设置为小于 3 的值(例如 -1)时,隐式流程才有效。但我仍然无法访问我的 ressourceServer 中的自定义端点。你解决了吗?
      • 我添加了整个代码。希望对您有所帮助,如果其他人发现任何潜在的安全风险,请告诉我们。
      猜你喜欢
      • 2014-07-07
      • 2015-03-05
      • 2017-03-18
      • 2014-12-09
      • 2012-12-27
      • 1970-01-01
      • 2014-05-26
      • 2019-06-07
      • 2020-07-26
      相关资源
      最近更新 更多