【问题标题】:Spring security OAuth2 - invalidate session after authenticationSpring security OAuth2 - 身份验证后使会话无效
【发布时间】:2016-01-05 12:16:41
【问题描述】:

我们正在使用 spring security OAuth2 保护 REST 服务。应用程序可以调用/oauth/authorize/oauth/token/rest-api 端点。 token 和 rest-api 端点是无状态的,不需要会话。

我们可以在用户通过身份验证后使会话无效吗?如果是这样,最好的方法是什么。我们希望用户在拨打/oauth/authorize 时始终登录。目前,只要会话存在,对/oauth/authorize 的调用就会跳过身份验证。

【问题讨论】:

  • 点击 /logout 对你不起作用吗?
  • 我们不想调用端点。在用户通过身份验证并生成令牌后,我们不希望会话再存在,并希望将其作为身份验证流程的一部分进行处理。
  • 你解决了吗?
  • 我们决定不使会话无效。如果会话已存在,则不会提示用户输入凭据。

标签: spring-security spring-security-oauth2


【解决方案1】:

据我了解,您在执行某些操作后尝试以编程方式注销。也许您应该查看SecurityContextLogoutHandler 并了解它是如何工作的。那里有一种注销方法。我认为将其称为建议将解决您的问题。

public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
      Assert.notNull(request, "HttpServletRequest required");
      if (invalidateHttpSession) {
          HttpSession session = request.getSession(false);
          if (session != null) {
              session.invalidate();
          }
      }

      SecurityContextHolder.clearContext();
  }

【讨论】:

    【解决方案2】:

    首先:在您的配置中为 oauth 声明带有令牌存储的 bean

    @Bean
    @Primary
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
    

    对于控制器方法,我们制作了以下类

    @Controller
     public class TokenController {
    
        @RequestMapping(value = "/oauth/token/revoke", method = RequestMethod.POST)
        public @ResponseBody void create(@RequestParam("token") String value) {
            this.revokeToken(value);
        }
    
        @Autowired
        TokenStore tokenStore;
    
        public boolean revokeToken(String tokenValue) {
            OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
            if (accessToken == null) {
                return false;
            }
            if (accessToken.getRefreshToken() != null) {
                tokenStore.removeRefreshToken(accessToken.getRefreshToken());
            }
            tokenStore.removeAccessToken(accessToken);
            return true;
        }
    }
    

    如果您不想使用这种方法,您可以获取当前用户的令牌自动装配Principal

        OAuth2Authentication authorization = (OAuth2Authentication) principal;
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authorization.getDetails();
        String token = details.getTokenValue();
    

    甚至自动装配OAuth2Authentication:

        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        String token = details.getTokenValue();
    

    【讨论】:

    • 我不认为这是一个有效的答案,只要它不会使会话无效。此外,从 tokenStore 中删除 token 并不适用于所有类型的 tokenStore:例如JwtTokenStore.
    【解决方案3】:

    理解这个问题有点老了,希望下面的内容对寻找问题正确答案的人有所帮助

    OP 询问的不是令牌失效,而是如何在用户身份验证成功通过并将有效的 access_token 或 authentication_code(用于后续获取 access_token)返回给客户端后立即使 Spring OAuth2 服务器上的 httpSession 失效。

    对于这个用例仍然没有开箱即用的解决方案。但是可以找到 spring-security-oauth 最活跃的贡献者 Dave Syer 的解决方法here on GitHub

    只需从那里复制代码:

    @Service
    @Aspect
    public class SessionInvalidationOauth2GrantAspect {
    
        private static final String FORWARD_OAUTH_CONFIRM_ACCESS = "forward:/oauth/confirm_access";
        private static final Logger logger = Logger.getLogger(SessionInvalidationOauth2GrantAspect.class);
    
        @AfterReturning(value = "within(org.springframework.security.oauth2.provider.endpoint..*) && @annotation(org.springframework.web.bind.annotation.RequestMapping)", returning = "result")
        public void authorizationAdvice(JoinPoint joinpoint, ModelAndView result) throws Throwable {
    
            // If we're not going to the confirm_access page, it means approval has been skipped due to existing access
            // token or something else and they'll be being sent back to app. Time to end session.
            if (!FORWARD_OAUTH_CONFIRM_ACCESS.equals(result.getViewName())) {
                invalidateSession();
            }
        }
    
        @AfterReturning(value = "within(org.springframework.security.oauth2.provider.endpoint..*) && @annotation(org.springframework.web.bind.annotation.RequestMapping)", returning = "result")
        public void authorizationAdvice(JoinPoint joinpoint, View result) throws Throwable {
            // Anything returning a view and not a ModelView is going to be redirecting outside of the app (I think). 
            // This happens after the authorize approve / deny page with the POST to /oauth/authorize. This is the time
            // to kill the session since they'll be being sent back to the requesting app.
            invalidateSession();
        }
    
        @AfterThrowing(value = "within(org.springframework.security.oauth2.provider.endpoint..*) &&  @annotation(org.springframework.web.bind.annotation.RequestMapping)", throwing = "error")
        public void authorizationErrorAdvice(JoinPoint joinpoint) throws Throwable {
            invalidateSession();
        }
    
        private void invalidateSession() {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            HttpSession session = request.getSession(false);
            if (session != null) {
                logger.warn(String.format("As part of OAuth application grant processing, invalidating session for request %s", request.getRequestURI()));
    
                session.invalidate();
                SecurityContextHolder.clearContext();
            }
        }
    
    }
    

    添加 pom.xml

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    

    另一种解决方案可能是将会话超时设置为某个非常小的值。实现这一点的最简单方法是将以下内容放入 application.yml 配置:

    server:
      session:
        timeout: 1
    

    但这不是理想的解决方案,因为提供者的最小值可能是 1(零是为无限会话保留的)并且它以分钟为单位而不是以秒为单位

    【讨论】:

    • 对某人有帮助。感谢分享!
    • 顺便说一句,我已经更改了一个 HandlerInterceptor 的 @Aspect 方法,它还验证了重定向 URL,因为该方面总是使会话无效,即使用户错误地输入了他/她的凭据。之后,就无法进行重定向了。
    • @ReginaldoSantos,感谢您的反馈!我们最近也切换到 HandlerInterceptor :-)
    【解决方案4】:

    我可以提供这样的选择(根据@de_xtr 推荐):

    import static org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes;
    
    @Slf4j
    @Component
    @Aspect
    public class InvalidateSessionAspect {
    
        private final LogoutHandler logoutHandler;
    
        public InvalidateSessionAspect() {
            logoutHandler = new SecurityContextLogoutHandler();
        }
    
        @Pointcut("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
        public void postAccessTokenPointcut() {
        }
    
        @AfterReturning(value = "postAccessTokenPointcut()", returning = "entity")
        public void invalidateSession(JoinPoint jp, Object entity) {
            log.debug("[d] Trying to invalidate the session...");
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) currentRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            logoutHandler.logout(request, null, null);
            log.debug("[d] Session has been invalidated");
        }
    }
    

    以及没有任何方面的选项:

    @Slf4j
    class LogoutHandlerInterceptor implements HandlerInterceptor {
        @Override
        public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object h, ModelAndView view) {
            HttpSession session = req.getSession(false);
            if (session != null) {
                log.debug("[d] Trying to invalidate the session...");
                session.invalidate();
                SecurityContext context = SecurityContextHolder.getContext();
                context.setAuthentication(null);
                SecurityContextHolder.clearContext();
                log.debug("[d] Session has been invalidated");
            }
        }
    }
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    
        //...
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.addInterceptor(new LogoutHandlerInterceptor())
                    // ... 
                    ;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-07-20
      • 2019-01-28
      • 2015-04-27
      • 2014-09-15
      • 2017-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多