Spring Security OAuth2 AngularJS | Logout Flow

Refresh

November 2018

Views

787 time

4

Referring to the logout flow in oauth2 spring-guides project, once the the user has authenticated using user/password for the first time, the credentials are not asked next time after logout.

How can I ensure that username/password are asked every time after a logout.

This is what I am trying to implement:-

  • OAuth2 server issuing JWT token using "authorization_code" grant type with auto approval. This has html/angularjs form to collect username/password.

  • UI/Webfront - Uses @EnableSSO. ALL its endpoints are authenticated i.e it does not have any unauthorized landing page/ui/link that user clicks to go to /uaa server. So hitting http://localhost:8080 instantly redirects you to http://localhost:9999/uaa and presents custom form to collect username/password.

  • Resource server - Uses @EnableResourceServer. Plain & simple REST api.

With the above approach I am not able to workout the logout flow. HTTP POST /logout to the UI application clears the session/auth in UI application but the users gets logged in again automatically ( as I have opted for auto approval for all scopes) without being asked for username password again.

Looking at logs and networks calls, it looks like that all the "oauth dance" happens all over again successfully without user being asked for username/password again and seems like the auth server remembers last auth token issued for a client ( using org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices? ).

How can I tell auth server to ask for username/password every time it is requested for code/token - stateless.

Or what is the best way to implement logout in my given scenario.

( To recreate somewhat near to my requirements, remove permitAll() part from the UiApplication and configure autoApproval in auth server of the mentioned boot project.)

github issue

3 answers

2

Как вы используете JWT маркеры, вы не можете отменить их. В качестве обходного пути, вы можете иметь выход из системы покоя конечной точки, которая будет хранить метку времени и идентификатор пользователя для завершения сеанса вызова.

Позже, вы можете сравнить время выхода из системы с JWT токенов времени выпуска, и решить, Wether разрешить вызов апи или нет.

2

Я также столкнулся ошибка , как вы описали , и я увидел решение от вопроса Spring загрузки oauth2 Single Sign Off . Я не имею в виду это единственное и глобальное решение истины.

Но в сценарии,

  • Сервер аутентификации форме входа в систему, и вы бы проверку подлинности от него
  • браузер по-прежнему поддерживать сеанс связи с сервером аутентификации
  • после того, как вы закончили процесс выхода из системы (отозвать маркеры, удалить куки ...) и попытаться повторно войти еще раз
  • Сервер аутентификации не отправить регистрационную форму и автоматически войдите в систему

Вы должны удалить информацию аутентификации от сеанса сервера аутентификации , как это описано ответ.

Ниже фрагменты являются как же настроить для решения

Клиент (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
    }

Пользовательский обработчик выхода из системы для клиентского приложения

@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);

    }

}

Я использовал JDBC tokenStore, так что мне нужно отменить tokens.At стороны сервера аутентификации, я добавил контроллер для обработки процессов выхода из системы

@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"));
    }

}

На SecurityConfig сервер авторизации, вам может понадобиться, чтобы этот адрес в качестве

http
    .requestMatchers()
        .antMatchers(
        "/login"
        ,"/ssoLogout"
        ,"/oauth/authorize"
        ,"/oauth/confirm_access");

Я надеюсь, что это может немного помочь вам.

1

Я понял, что перенаправление к контроллеру, когда вы выйдите из клиентского приложения, а затем программно выйти из системы на вашем authserver делает трюк. Это моя конфигурация на приложение клиента:

@Configuration
@EnableOAuth2Sso
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .logout()
            .logoutSuccessUrl(logoutUrl)
            .and().authorizeRequests().anyRequest().authenticated();
    }
}

и это моя конфигурация на моем authserver (только контроллер обработки / выход конечной точки):

@Controller
public class LogoutController {
    public LogoutController() {
    }

    @RequestMapping({"/exit"})
    public void exit(HttpServletRequest request, HttpServletResponse response) {
        (new SecurityContextLogoutHandler()).logout(request, null, null);

        try {
            response.sendRedirect(request.getHeader("referer"));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

Вот образец приложение , которое показывает полную реализацию с помощью JWT. Проверьте его и дайте нам знать , если это поможет вам.