【问题标题】:Spring-Security : Not acknowledging cookie set in RestTemplateSpring-Security:不承认在 RestTemplate 中设置的 cookie
【发布时间】:2015-09-29 08:44:59
【问题描述】:

我正在开发一个连接到 Spring-MVC 服务器的 Java 应用程序,使用 Spring-Security 进行身份验证/授权。登录部分有效,我在 Java 应用程序中得到了一个 JSESSIONID,但是当我向安全资源发出请求时,它失败了,Spring-Security 无法找到任何登录用户。我在这里做错了什么?

security-applicationContext.xml:

 <security:http pattern="/resources/**" security="none"/>

    <security:http create-session="ifRequired" use-expressions="true" auto-config="false" disable-url-rewriting="true">
        <security:form-login login-page="/login" login-processing-url="/j_spring_security_check"
                             default-target-url="/dashboard" always-use-default-target="false"
                             authentication-failure-url="/denied"/>
        <security:remember-me key="_spring_security_remember_me" user-service-ref="userDetailsService"
                              token-validity-seconds="1209600" data-source-ref="dataSource"/>
        <security:logout delete-cookies="JSESSIONID" invalidate-session="true" logout-url="/j_spring_security_logout"/>
        <!--<security:intercept-url pattern="/**" requires-channel="https"/>-->
        <security:port-mappings>
            <security:port-mapping http="8080" https="8443"/>
        </security:port-mappings>
        <security:logout logout-url="/logout" logout-success-url="/" success-handler-ref="myLogoutHandler"/>


        <security:session-management session-fixation-protection="migrateSession">
            <security:concurrency-control session-registry-ref="sessionRegistry" max-sessions="5" expired-url="/login"/>
        </security:session-management>

    </security:http>

  <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="restaurantauthenticationprovider"/>
        <security:authentication-provider ref="userauthenticationprovider"/>
    </security:authentication-manager>

    <beans:bean id="encoder"
                class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
        <beans:constructor-arg name="strength" value="11"/>
    </beans:bean>

    <beans:bean id="restaurantauthenticationprovider"
                class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="userDetailsService" ref="LoginServiceImpl"/>
        <beans:property name="passwordEncoder" ref="encoder"/>
    </beans:bean>

    <beans:bean id="userauthenticationprovider"
                class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
        <beans:property name="userDetailsService" ref="UserLoginServiceImpl"/>
        <beans:property name="passwordEncoder" ref="encoder"/>
    </beans:bean>

因为我有 2 个表要检查从哪个表登录,所以我有 2 个 DAOAuthenticationProviders。

UserLoginServiceImpl :

@Transactional
@Service("loginuserDetailsService")
public class UserLoginServiceImpl implements UserDetailsService {


     @Autowired
     private PersonDAO personDAO;
     @Autowired
     private UserAssembler userAssembler;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException,DataAccessException {
        System.out.println("Username is "+username);
        Person person = this.personDAO.findPersonByUserName(username.toLowerCase());
        if(person == null) { throw new UsernameNotFoundException("Wrong username or password");} 
        return userAssembler.buildUserFromUserEntity(person);
    }
}

汇编器:

@Service("userassembler")
@Transactional
public class UserAssembler {

    @Transactional
    User buildUserFromUserEntity(Person userEntity){
        System.out.println("We are in Userassembler"+userEntity.getEmail());
        String username = userEntity.getUsername().toLowerCase();
        String password = userEntity.getPassword();

        boolean enabled = userEntity.isEnabled();
        boolean accountNonExpired = userEntity.isAccountNonExpired();
        boolean credentialsNonExpired = userEntity.isCredentialsNonExpired();
        boolean accountNonLocked = userEntity.isAccountNonLocked();

        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        return new User(username,password,enabled,accountNonExpired,credentialsNonExpired,accountNonLocked,authorities);
    }
}

以上是配置,现在我将放置失败的其余代码:

Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d("Username is ", username);
                String jsessionid = rest.execute("http://192.168.178.60:8080/j_spring_security_check", HttpMethod.POST,
                        new RequestCallback() {
                            @Override
                            public void doWithRequest(ClientHttpRequest request) throws IOException {
                                request.getBody().write(("j_username=" + username + "&j_password=" + password).getBytes());
                            }
                        }, new ResponseExtractor<String>() {
                            @Override
                            public String extractData(ClientHttpResponse response) throws IOException {
                                List<String> cookies = response.getHeaders().get("Cookie");
                                if (cookies == null) {
                                    cookies = response.getHeaders().get("Set-Cookie");
                                }
                                String cookie = cookies.get(cookies.size() - 1);
                                System.out.println("Cookie is " + cookie);
// The method below gets me which user is logged in, and I always get null for Controller method.
                                reply = rest.getForObject(
                                        "http://192.168.178.60:8080/dashboard", String.class);

                                int start = cookie.indexOf('=');
                                int end = cookie.indexOf(';');
                                return cookie.substring(start + 1, end);
                            }
                        });

            }
        });
        thread.start();

更新 最后,有效的代码:

// 我从服务器获取 cookie,我为每个请求手动设置,cookie 是一个静态易失性字符串。

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", "JSESSIONID=" + StaticRestTemplate.jsessionid);
HttpEntity requestEntity = new HttpEntity(null, requestHeaders);

ResponseEntity rssResponse = rest.exchange(
                                  "http://192.168.178.60:8080/dashboard",
                                   HttpMethod.GET,
                                   requestEntity,
                                   String.class);

String abc = (String) rssResponse.getBody();

【问题讨论】:

  • 您的个人服务是什么样的?如果您将方法更改为: String checkWichUserLoggedIn(Principal p) { return p == null 会发生什么?空:p.getName(); }
  • @RobWinch :我把它当作空......因为找不到用户匿名用户。我已经更新了我的主要帖子中的代码,顺便说一句,当我打印身份验证详细信息时,会话 ID 为空。我了解 RestTemplate 在您进行身份验证时会在内部设置一个 cookie,但情况似乎并非如此。
  • 你真的得到了一个 JSESSIONID 吗? HTTP 状态码是什么?您使用的是哪个版本的 Spring Security?
  • 我在登录时得到 jsessionid 并成功。

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


【解决方案1】:

默认情况下,Spring 的 RestTemplate 不跟踪 cookie。这可以确保您不会意外地从一个用户代表另一个用户传递 cookie(即 JSESSIONID)(即考虑在许多用户使用相同 RestTemplate 的服务器上使用 RestTemplate)。

如果你想这样做,你可以使用类似这样的方式进行配置:

RestTemplate rest = new RestTemplate();

// initialize the RequestFactory to allow cookies
HttpClient httpClient = HttpClientBuilder.create().build();
ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
rest.setRequestFactory(factory);

MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("username", "user");
map.add("password", "password");

String result = rest.postForObject("http://localhost:8080/login", map, String.class);

String hello = rest.postForObject("http://localhost:8080/", map, String.class);

assertThat(hello).isEqualTo("Hello");

要使用此代码,您需要确保您的类路径中有 httpclient。例如,如果您使用 Maven,以下内容可能在您的 pom.xml 中:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5</version>
</dependency>

显然,您需要确保包含适用于您的依赖项的 httpclient 版本。

【讨论】:

  • httpclient 已被弃用,你有什么新东西吗?我找到了 stackoverflow.com/questions/29058727/… ,但我找不到如何为 Rest 设置数据。
  • 如果您使用的是最新的 httpclient (4.5),则不应弃用它
  • 但是httpclient有httpcore的要求,不是4.5...另外,当我使用这些库时,我得到了其他错误,所以我使用了android-httpclient。然后,我遇到了一些问题,最后,我使用了一些我将在帖子底部提到的东西。如果你知道我可以如何优化它,请告诉我。
  • 而且,即使在 4.5 上,它也已被弃用...我正在尝试使用 4.5 再次选择您的选项,因为最好设置一次然后忘记...
  • 使用 4.5 时出错:07-13 15:41:06.553 5465-6052/com.example.TestLunch E/AndroidRuntime:致命异常:线程 171 进程:com.example.TestLunch,PID: 5465 java.lang.NoClassDefFoundError:解析失败:Lorg/apache/http/util/Args;
猜你喜欢
  • 1970-01-01
  • 2011-05-17
  • 2013-12-19
  • 2016-11-15
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 1970-01-01
  • 2014-11-27
相关资源
最近更新 更多