春季安全OAuth2 AngularJS | 登出流程

Kum*_*hav 5 spring-security spring-security-oauth2

参考oauth2 spring-guides项目中的注销流程,一旦用户首次使用用户名/密码进行了身份验证,则注销后下次将不要求提供凭据。

如何确保每次注销后都要求输入用户名/密码。

这就是我要实现的:

  • OAuth2服务器使用具有自动批准功能的“ authorization_code”授予类型发布JWT令牌。这具有html / angularjs表单来收集用户名/密码。

  • UI / Webfront-使用@EnableSSO。其所有端点均已通过身份验证,即,它没有用户单击以转到/ uaa服务器的任何未经授权的登录页面/ ui /链接。因此,点击http:// localhost:8080会 立即将您重定向到http:// localhost:9999 / uaa并显示自定义表单以收集用户名/密码。

  • 资源服务器-使用@EnableResourceServer。普通和简单的REST API。

使用上述方法,我无法锻炼注销流程。到UI应用程序的HTTP POST / logout清除了UI应用程序中的会话/身份验证,但用户再次自动登录(因为我选择了所有范围的自动批准),而无需再次询问用户名密码。

查看日志和网络呼叫,似乎所有“ oauth舞动”都成功地再次发生,而无需再次要求用户输入用户名/密码,并且似乎auth服务器记住了为客户端发布的最后一个auth令牌(使用org.springframework .security.oauth2.provider.code.InMemoryAuthorizationCodeServices?)。

我如何告诉认证服务器每次要求输入用户名/密码的代码/令牌-无状态。

还是在给定方案中实现注销的最佳方法是什么。

(要重新创建一些接近我的要求的内容,请permitAll()从UiApplication中删除一部分,然后autoApproval在上述启动项目的身份验证服务器中进行配置。)

github问题

Cat*_*ysm 3

我也遇到了您所描述的错误,并且我从问题 Spring Boot OAuth2 Single Sign Off中看到了解决方案。我并不是说这是唯一的、全球性的真相解决方案。

但在场景中,

  • 身份验证服务器有登录表单,您可以通过它进行身份验证
  • 浏览器仍然保持与身份验证服务器的会话
  • 完成注销过程后(撤销令牌、删除 cookie...)并尝试再次重新登录
  • 身份验证服务器不发送登录表单并自动登录

您需要从身份验证服务器的会话中删除身份验证信息,如此答案所述。

下面的片段是我如何配置解决方案

客户端(在您的情况下为 UI 应用程序)应用程序的 WebSecurityConfig

...
@Value("${auth-server}/ssoLogout")
private String logoutUrl;
@Autowired
private CustomLogoutHandler logoutHandler;
...
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/", "/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .logout()
                .logoutSuccessUrl(logoutUrl)
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                .addLogoutHandler(logoutHandler)
        .and()      
            .csrf()
                .csrfTokenRepository(csrfTokenRepository())
        .and()
            .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
        // @formatter:on
    }
Run Code Online (Sandbox Code Playgroud)

客户端应用程序的自定义注销处理程序

@Component
public class CustomLogoutHandler implements LogoutHandler {

    private static Logger logger = Logger.getLogger(CustomLogoutHandler.class);

    @Value("${auth-server}/invalidateTokens")
    private String logoutUrl;

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

        logger.debug("Excution CustomLogoutHandler for " + authentication.getName());
        Object details = authentication.getDetails();
        if (details.getClass().isAssignableFrom(OAuth2AuthenticationDetails.class)) {

            String accessToken = ((OAuth2AuthenticationDetails) details).getTokenValue();
            RestTemplate restTemplate = new RestTemplate();

            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
            params.add("access_token", accessToken);

            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "bearer " + accessToken);

            HttpEntity<Object> entity = new HttpEntity<>(params, headers);

            HttpMessageConverter<?> formHttpMessageConverter = new FormHttpMessageConverter();
            HttpMessageConverter<?> stringHttpMessageConverternew = new StringHttpMessageConverter();
            restTemplate.setMessageConverters(Arrays.asList(new HttpMessageConverter[] { formHttpMessageConverter, stringHttpMessageConverternew }));
            try {
                ResponseEntity<String> serverResponse = restTemplate.exchange(logoutUrl, HttpMethod.POST, entity, String.class);
                logger.debug("Server Response : ==> " + serverResponse);
            } catch (HttpClientErrorException e) {
                logger.error("HttpClientErrorException invalidating token with SSO authorization server. response.status code:  " + e.getStatusCode() + ", server URL: " + logoutUrl);
            }
        }
        authentication.setAuthenticated(false);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        new SecurityContextLogoutHandler().logout(request, response, auth);

    }

}
Run Code Online (Sandbox Code Playgroud)

我使用了 JDBC tokenStore,所以我需要撤销令牌。在身份验证服务器端,我添加了一个控制器来处理注销过程

@Controller
public class AuthenticationController {

    private static Logger logger = Logger.getLogger(AuthenticationController.class);

    @Resource(name = "tokenStore")
    private TokenStore tokenStore;

    @Resource(name = "approvalStore")
    private ApprovalStore approvalStore;

    @RequestMapping(value = "/invalidateTokens", method = RequestMethod.POST)
    public @ResponseBody Map<String, String> revokeAccessToken(HttpServletRequest request, HttpServletResponse response, @RequestParam(name = "access_token") String accessToken, Authentication authentication) {
        if (authentication instanceof OAuth2Authentication) {
            logger.info("Revoking Approvals ==> " + accessToken);
            OAuth2Authentication auth = (OAuth2Authentication) authentication;
            String clientId = auth.getOAuth2Request().getClientId();
            Authentication user = auth.getUserAuthentication();
            if (user != null) {
                Collection<Approval> approvals = new ArrayList<Approval>();
                for (String scope : auth.getOAuth2Request().getScope()) {
                    approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED));
                }
                approvalStore.revokeApprovals(approvals);
            }
        }
        logger.info("Invalidating access token :- " + accessToken);
        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
        if (oAuth2AccessToken != null) {
            if (tokenStore instanceof JdbcTokenStore) {
                logger.info("Invalidating Refresh Token :- " + oAuth2AccessToken.getRefreshToken().getValue());
                ((JdbcTokenStore) tokenStore).removeRefreshToken(oAuth2AccessToken.getRefreshToken());
                tokenStore.removeAccessToken(oAuth2AccessToken);
            }
        }
        Map<String, String> ret = new HashMap<>();
        ret.put("removed_access_token", accessToken);
        return ret;
    }

    @GetMapping("/ssoLogout")
    public void exit(HttpServletRequest request, HttpServletResponse response) throws IOException {
        new SecurityContextLogoutHandler().logout(request, null, null);
        // my authorization server's login form can save with remember-me cookie 
        Cookie cookie = new Cookie("my_rememberme_cookie", null);
        cookie.setMaxAge(0);
        cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
        response.addCookie(cookie);
        response.sendRedirect(request.getHeader("referer"));
    }

}
Run Code Online (Sandbox Code Playgroud)

在授权服务器的 SecurityConfig 中,您可能需要允许此 url 作为

http
    .requestMatchers()
        .antMatchers(
        "/login"
        ,"/ssoLogout"
        ,"/oauth/authorize"
        ,"/oauth/confirm_access");
Run Code Online (Sandbox Code Playgroud)

我希望这对你有一点帮助。