Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/redis/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Spring security Spring会话/Redis和Oauth2不能一起工作_Spring Security_Redis_Oauth 2.0_Spring Security Oauth2_Spring Session - Fatal编程技术网

Spring security Spring会话/Redis和Oauth2不能一起工作

Spring security Spring会话/Redis和Oauth2不能一起工作,spring-security,redis,oauth-2.0,spring-security-oauth2,spring-session,Spring Security,Redis,Oauth 2.0,Spring Security Oauth2,Spring Session,Oauth2和Redis不能很好地配合使用。一旦我启用了Spring会话,在我经过身份验证(OIDC)并发送回应用程序后,就会创建两个会话ID——一个来自Redis的JSESSIONID,另一个来自Spring Security Oauth。一旦我禁用Redis/Spring会话,一切都很好 我创建了一个非常小的Maven应用程序,可从以下网站下载: 如果我在本地主机上通过Jetty和Redis运行应用程序,我就能够在本地重现问题。如Firefox屏幕截图所示,创建了两个会话cookie:

Oauth2和Redis不能很好地配合使用。一旦我启用了Spring会话,在我经过身份验证(OIDC)并发送回应用程序后,就会创建两个会话ID——一个来自Redis的JSESSIONID,另一个来自Spring Security Oauth。一旦我禁用Redis/Spring会话,一切都很好

我创建了一个非常小的Maven应用程序,可从以下网站下载:

如果我在本地主机上通过Jetty和Redis运行应用程序,我就能够在本地重现问题。如Firefox屏幕截图所示,创建了两个会话cookie:

我遵循了Baeldung的指南,但做了一些小改动,使应用程序与我们的OIDC提供商兼容。

所有这些类都可以在zip文件中找到(参见上面的链接)。最重要的是:

RedisConfiguration.java

@Configuration
@EnableRedisHttpSession(redisNamespace = "oidc", maxInactiveIntervalInSeconds = 10800)
public class RedisConfiguration {

  @Bean
  public LettuceConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory("localhost", 6379);
  }

}
@Configuration
@EnableOAuth2Client
public class FeideOpenIdConnectConfig {

  @Value("${feide.auth.clientId}")
  private String clientId;

  @Value("${feide.auth.clientSecret}")
  private String clientSecret;

  @Value("${feide.auth.accessTokenUri}")
  private String accessTokenUri;

  @Value("${feide.auth.userAuthorizationUri}")
  private String userAuthorizationUri;

  @Value("${feide.auth.preEstablishedRedirectUri}")
  private String preEstablishedRedirectUri;


  @Bean
  public OAuth2ProtectedResourceDetails feideOpenId() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    details.setClientId(clientId);
    details.setClientSecret(clientSecret);
    details.setAccessTokenUri(accessTokenUri);
    details.setUserAuthorizationUri(userAuthorizationUri);
    details.setScope(Arrays.asList("openid", "email", "userid-feide", "profile", "groups"));
    details.setPreEstablishedRedirectUri(preEstablishedRedirectUri);
    details.setUseCurrentUri(false);
    details.setGrantType("authorization_code");
    return details;
  }


  @Bean
  public OAuth2RestTemplate feideOpenIdTemplate(OAuth2ClientContext clientContext) {
    return new OAuth2RestTemplate(feideOpenId(), clientContext);
  }

}
public class FeideConnectFilter extends OAuth2ClientAuthenticationProcessingFilter {

  public FeideConnectFilter(String defaultFilterProcessesUrl) {
    super(defaultFilterProcessesUrl);
  }


  @Override
  public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

    OAuth2AccessToken accessToken;
    try {
      accessToken = restTemplate.getAccessToken();
    } catch (OAuth2Exception e) {
      throw new BadCredentialsException("Could not obtain access token", e);
    }
    try {
      String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
      Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier("https://auth.dataporten.no/openid/jwks"));

      @SuppressWarnings("unchecked")
      Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);

      verifyClaims(authInfo, "https://auth.dataporten.no");

      request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
      request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());

      OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
      return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    } catch (Exception e) {
      throw new BadCredentialsException("Could not obtain user details from token", e);
    }
  }


  @Override
  protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
    if (super.requiresAuthentication(request, response)) {
      return true;
    }

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    // Already authenticated:
    if (authentication != null) {
      return false;
    }
    OAuth2AccessToken accessToken = restTemplate.getAccessToken();
    if (accessToken == null) {
        return true;
    }
    return true;
  }


  @SuppressWarnings("rawtypes")
  protected void verifyClaims(final Map claims, final String issuer) {
    int exp = (Integer) claims.get("exp");
    Date expireDate = new Date(exp * 1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) ||
            !claims.get("aud").equals(restTemplate.getResource().getClientId())) {
      throw new RuntimeException("Invalid claims");
    }
  }


  protected RsaVerifier verifier(final String jwkSigningUri) throws Exception {
    CustomUrlJwkProvider provider = new CustomUrlJwkProvider(new URL(jwkSigningUri));
    Jwk jwk = provider.getJwk();
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
  }


  protected HttpHeaders getHttpHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + restTemplate.getAccessToken());
    return headers;
  }

}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Inject
  private OAuth2RestTemplate restTemplate;


  @Bean
  public FeideConnectFilter feideConnectFilter() {
    FeideConnectFilter filter = new FeideConnectFilter("/oauth/login");
    filter.setRestTemplate(restTemplate);
    return filter;
  }


  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
        .addFilterAfter(feideConnectFilter(), OAuth2ClientContextFilter.class)
        .httpBasic()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/login"))
        .and()
        .authorizeRequests()
        .anyRequest().authenticated();
  }

}
feideopendconnectconfig.java

@Configuration
@EnableRedisHttpSession(redisNamespace = "oidc", maxInactiveIntervalInSeconds = 10800)
public class RedisConfiguration {

  @Bean
  public LettuceConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory("localhost", 6379);
  }

}
@Configuration
@EnableOAuth2Client
public class FeideOpenIdConnectConfig {

  @Value("${feide.auth.clientId}")
  private String clientId;

  @Value("${feide.auth.clientSecret}")
  private String clientSecret;

  @Value("${feide.auth.accessTokenUri}")
  private String accessTokenUri;

  @Value("${feide.auth.userAuthorizationUri}")
  private String userAuthorizationUri;

  @Value("${feide.auth.preEstablishedRedirectUri}")
  private String preEstablishedRedirectUri;


  @Bean
  public OAuth2ProtectedResourceDetails feideOpenId() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    details.setClientId(clientId);
    details.setClientSecret(clientSecret);
    details.setAccessTokenUri(accessTokenUri);
    details.setUserAuthorizationUri(userAuthorizationUri);
    details.setScope(Arrays.asList("openid", "email", "userid-feide", "profile", "groups"));
    details.setPreEstablishedRedirectUri(preEstablishedRedirectUri);
    details.setUseCurrentUri(false);
    details.setGrantType("authorization_code");
    return details;
  }


  @Bean
  public OAuth2RestTemplate feideOpenIdTemplate(OAuth2ClientContext clientContext) {
    return new OAuth2RestTemplate(feideOpenId(), clientContext);
  }

}
public class FeideConnectFilter extends OAuth2ClientAuthenticationProcessingFilter {

  public FeideConnectFilter(String defaultFilterProcessesUrl) {
    super(defaultFilterProcessesUrl);
  }


  @Override
  public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

    OAuth2AccessToken accessToken;
    try {
      accessToken = restTemplate.getAccessToken();
    } catch (OAuth2Exception e) {
      throw new BadCredentialsException("Could not obtain access token", e);
    }
    try {
      String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
      Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier("https://auth.dataporten.no/openid/jwks"));

      @SuppressWarnings("unchecked")
      Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);

      verifyClaims(authInfo, "https://auth.dataporten.no");

      request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
      request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());

      OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
      return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    } catch (Exception e) {
      throw new BadCredentialsException("Could not obtain user details from token", e);
    }
  }


  @Override
  protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
    if (super.requiresAuthentication(request, response)) {
      return true;
    }

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    // Already authenticated:
    if (authentication != null) {
      return false;
    }
    OAuth2AccessToken accessToken = restTemplate.getAccessToken();
    if (accessToken == null) {
        return true;
    }
    return true;
  }


  @SuppressWarnings("rawtypes")
  protected void verifyClaims(final Map claims, final String issuer) {
    int exp = (Integer) claims.get("exp");
    Date expireDate = new Date(exp * 1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) ||
            !claims.get("aud").equals(restTemplate.getResource().getClientId())) {
      throw new RuntimeException("Invalid claims");
    }
  }


  protected RsaVerifier verifier(final String jwkSigningUri) throws Exception {
    CustomUrlJwkProvider provider = new CustomUrlJwkProvider(new URL(jwkSigningUri));
    Jwk jwk = provider.getJwk();
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
  }


  protected HttpHeaders getHttpHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + restTemplate.getAccessToken());
    return headers;
  }

}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Inject
  private OAuth2RestTemplate restTemplate;


  @Bean
  public FeideConnectFilter feideConnectFilter() {
    FeideConnectFilter filter = new FeideConnectFilter("/oauth/login");
    filter.setRestTemplate(restTemplate);
    return filter;
  }


  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
        .addFilterAfter(feideConnectFilter(), OAuth2ClientContextFilter.class)
        .httpBasic()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/login"))
        .and()
        .authorizeRequests()
        .anyRequest().authenticated();
  }

}
FeideConnectFilter.java

@Configuration
@EnableRedisHttpSession(redisNamespace = "oidc", maxInactiveIntervalInSeconds = 10800)
public class RedisConfiguration {

  @Bean
  public LettuceConnectionFactory connectionFactory() {
    return new LettuceConnectionFactory("localhost", 6379);
  }

}
@Configuration
@EnableOAuth2Client
public class FeideOpenIdConnectConfig {

  @Value("${feide.auth.clientId}")
  private String clientId;

  @Value("${feide.auth.clientSecret}")
  private String clientSecret;

  @Value("${feide.auth.accessTokenUri}")
  private String accessTokenUri;

  @Value("${feide.auth.userAuthorizationUri}")
  private String userAuthorizationUri;

  @Value("${feide.auth.preEstablishedRedirectUri}")
  private String preEstablishedRedirectUri;


  @Bean
  public OAuth2ProtectedResourceDetails feideOpenId() {
    AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
    details.setClientId(clientId);
    details.setClientSecret(clientSecret);
    details.setAccessTokenUri(accessTokenUri);
    details.setUserAuthorizationUri(userAuthorizationUri);
    details.setScope(Arrays.asList("openid", "email", "userid-feide", "profile", "groups"));
    details.setPreEstablishedRedirectUri(preEstablishedRedirectUri);
    details.setUseCurrentUri(false);
    details.setGrantType("authorization_code");
    return details;
  }


  @Bean
  public OAuth2RestTemplate feideOpenIdTemplate(OAuth2ClientContext clientContext) {
    return new OAuth2RestTemplate(feideOpenId(), clientContext);
  }

}
public class FeideConnectFilter extends OAuth2ClientAuthenticationProcessingFilter {

  public FeideConnectFilter(String defaultFilterProcessesUrl) {
    super(defaultFilterProcessesUrl);
  }


  @Override
  public Authentication attemptAuthentication(final HttpServletRequest request, final HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

    OAuth2AccessToken accessToken;
    try {
      accessToken = restTemplate.getAccessToken();
    } catch (OAuth2Exception e) {
      throw new BadCredentialsException("Could not obtain access token", e);
    }
    try {
      String idToken = accessToken.getAdditionalInformation().get("id_token").toString();
      Jwt tokenDecoded = JwtHelper.decodeAndVerify(idToken, verifier("https://auth.dataporten.no/openid/jwks"));

      @SuppressWarnings("unchecked")
      Map<String, String> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);

      verifyClaims(authInfo, "https://auth.dataporten.no");

      request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
      request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());

      OpenIdConnectUserDetails user = new OpenIdConnectUserDetails(authInfo, accessToken);
      return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    } catch (Exception e) {
      throw new BadCredentialsException("Could not obtain user details from token", e);
    }
  }


  @Override
  protected boolean requiresAuthentication(final HttpServletRequest request, final HttpServletResponse response) {
    if (super.requiresAuthentication(request, response)) {
      return true;
    }

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    // Already authenticated:
    if (authentication != null) {
      return false;
    }
    OAuth2AccessToken accessToken = restTemplate.getAccessToken();
    if (accessToken == null) {
        return true;
    }
    return true;
  }


  @SuppressWarnings("rawtypes")
  protected void verifyClaims(final Map claims, final String issuer) {
    int exp = (Integer) claims.get("exp");
    Date expireDate = new Date(exp * 1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) ||
            !claims.get("aud").equals(restTemplate.getResource().getClientId())) {
      throw new RuntimeException("Invalid claims");
    }
  }


  protected RsaVerifier verifier(final String jwkSigningUri) throws Exception {
    CustomUrlJwkProvider provider = new CustomUrlJwkProvider(new URL(jwkSigningUri));
    Jwk jwk = provider.getJwk();
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
  }


  protected HttpHeaders getHttpHeaders() {
    HttpHeaders headers = new HttpHeaders();
    headers.set("Authorization", "Bearer " + restTemplate.getAccessToken());
    return headers;
  }

}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Inject
  private OAuth2RestTemplate restTemplate;


  @Bean
  public FeideConnectFilter feideConnectFilter() {
    FeideConnectFilter filter = new FeideConnectFilter("/oauth/login");
    filter.setRestTemplate(restTemplate);
    return filter;
  }


  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
        .addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
        .addFilterAfter(feideConnectFilter(), OAuth2ClientContextFilter.class)
        .httpBasic()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/oauth2/login"))
        .and()
        .authorizeRequests()
        .anyRequest().authenticated();
  }

}
过滤器(在WebInitializer.java中)按以下顺序添加:

private void addFilters(final ServletContext container, final WebApplicationContext applicationContext) {
  container.addFilter("springSessionRepositoryFilter", DelegatingFilterProxy.class).addMappingForUrlPatterns(null, false, "/*");
  container.addFilter("springSecurityFilterChain", DelegatingFilterProxy.class).addMappingForUrlPatterns(null, false, "/*");
}

如果我使用SpringSecurity自己的OIDC实现(在版本5中引入),那么OIDC可以与Spring会话一起工作。顺便说一句,它的实现非常简单,几乎没有任何代码。Spring安全团队在Spring安全中添加了对OIDC和SAML2.0的支持,这一点做得非常好。换句话说,我找到了一个解决办法