【问题标题】:Logout with Rest Template in Spring Security Application在 Spring Security 应用程序中使用 Rest 模板注销
【发布时间】:2017-08-10 17:43:06
【问题描述】:

我正在为我的应用程序编写一个客户端。 Spring 堆栈是 Spring 4 和 Spring Security 4(主要部分)。

我尝试通过以下方式从我的应用程序中注销:

HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> entity = new HttpEntity<>("_csrf=" + csrfToken,
                                             httpHeaders);
restTemplate.postForEntity(appUrl + "/logout", entity, String.class);

RestTemplate 对象是通过以下方式创建的(当然是在登录之前):

new RestTemplate(new HttpComponentsClientHttpRequestFactory())

但我在服务器上得到以下异常:

org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported at 
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:207) at 
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:374) at 
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:314) at 
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:61) at 
org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:352) 

当我尝试登录应用程序时遇到以下异常。唯一的 我设法做到这一点的方法是获取登录页面并从那里获取 CSRF 令牌。我尝试通过以下方式从服务器获取令牌并将其返回给客户端:

@RequestMapping(value = "/api/csrf", method = RequestMethod.GET)
public String csrf(HttpServletRequest httpServletRequest) {
    return ((CsrfToken) httpServletRequest.getAttribute(CsrfToken.class.getName())).getToken();
}

但是有了这个令牌,我总是遇到同样的异常。

现在我想至少以任何方式实现注销,但与RestTemplate 正确登录相关的注意事项也值得赞赏。谢谢!

更新:添加安全配置

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final DataSource dataSource;
    private final UserDetailsService splittingRolesUserDetails;
    private final AccessDeniedHandler accessDeniedHandler;

    @Autowired
    public SecurityConfig(DataSource dataSource, UserDetailsService splittingRolesUserDetails,
                          AccessDeniedHandler accessDeniedHandler) {
        this.dataSource = dataSource;
        this.splittingRolesUserDetails = splittingRolesUserDetails;
        this.accessDeniedHandler = accessDeniedHandler;
    }

    // overrides role prefix in case .access() in httpSecurity configuration
    // just because it is needed in the task. hasRole() won't work
    // as there are used different voters in AffirmativeBased.
    // link to the related issue on GitHub:
    // https://github.com/spring-projects/spring-security/issues/3701
    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults("");
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
            .authenticationProvider(authenticationProvider())
            .jdbcAuthentication()
            .dataSource(dataSource)
            .usersByUsernameQuery("select user_name, password, true from user where username=?");
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(splittingRolesUserDetails);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
            .authorizeRequests()
                .antMatchers("/login/**").permitAll()
                .antMatchers("/api/csrf").permitAll()
                .antMatchers("/api/ticket/event**").access("hasRole('" + Role.BOOKING_MANAGER.toString() + "')")
                .anyRequest().access("hasRole('" + Role.REGISTERED_USER.toString() + "')")
            .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/event")
                .permitAll()
            .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)
                .accessDeniedPage("/403")
            .and()
                .rememberMe()
                .userDetailsService(splittingRolesUserDetails);
    }
}

【问题讨论】:

  • 登录时出现什么异常,您是如何登录的,您是否保存了cookie?当您请求注销 url 时,您是否将 cookie 添加到标头?>“当我尝试登录应用程序时出现以下异常”
  • @chaoluo 对于登录,我遇到了与注销时相同的异常,我没有保存 cookie。执行注销时,我正在设置 X-CSRF-TOKEN 标头(尽管据我了解这里不需要),添加“_csrf =”表单参数并设置 Content-Type=application/x-www-form-urlencoded。
  • Http 是一个无状态协议,如果你想实现自己的客户端,你应该使用 cookie(例如 JSESSIONID) 请求。但是为什么你不为你的客户端改变另一个身份验证,(例如 HTTP 基本/承载身份验证)?
  • @chaoluo 我正在使用这个调用创建一个 RestTemplate 对象:new RestTemplate(new HttpComponentsClientHttpRequestFactory())。而且我能够登录到应用程序并执行一些请求。所以我来宾这是添加 JSESSIONID 的事情。抱歉,我不太明白如何更改客户端的身份验证?
  • 服务器也应该做一些更改以支持基本身份验证。

标签: spring spring-mvc spring-security csrf resttemplate


【解决方案1】:

无需从不安全的端点发送您的令牌,这与最初使用令牌的原则相矛盾。您可以通过将其添加到配置中来将令牌存储在仅具有 HTTP 访问权限的 cookie 中:

.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());

然后您可以从名为 XSRF-TOKEN 的 cookie 中检索它。

【讨论】:

  • 感谢您的回答。但我不确定我做对了。在我看来,csrf 令牌用于保护服务器免受未经授权的请求。为什么要为此目的保护诸如浏览器或 RestTemplate 对象之类的端点?另外我的配置和你建议的配置有什么区别?据我了解,csrf 令牌无论如何都会被验证,但这次它将存储在具有其他名称的 cookie 中。
  • 我认为你把 csrf 弄错了。 csrf 令牌用于防止跨站点请求伪造,它利用服务器端会话,并尝试模仿请求以窃取信息或造成损坏。如果令牌可通过您的端点获得,那么您很容易受到 csrf 攻击。 Cookie 只能由您的应用访问。
  • 那谢谢你的解释!我想我真的应该在这里深入挖掘。但是我已经尝试了您的解决方案,但它似乎不适用于注销,尽管我可以使用我的 /api/csrf 方法使登录更加准确。我犯了同样的错误。不,我设置了 X-XSRF-TOKEN 标头。我做错了吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-28
  • 2015-06-28
  • 2013-07-21
  • 2014-03-01
  • 2013-04-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多