@AuthenticationPrincipal 对象返回会话值

Posted

技术标签:

【中文标题】@AuthenticationPrincipal 对象返回会话值【英文标题】:@AuthenticationPrincipal object return session value 【发布时间】:2014-12-18 20:40:47 【问题描述】:

@AuthenticationPrincipal 对象返回之前存储在会话中的值。

Spring boot + spring security oauth REST 服务器。 https://github.com/legshort/spring-boot-sample

这两个 REST 方法在控制器中。问题是当我运行测试代码时,最后一个参数,deleteUser() 处的 userDetailsImpl 与 updateUser() 处的 userDetailsImpl 的值相同。

@RequestMapping(method = RequestMethod.PUT, value = "/users/userId")
public ResponseEntity updateUser(@PathVariable Long userId,
        @AuthenticationPrincipal UserDetailsImpl userDetailsImpl,
        @Valid @RequestBody UserUpdateForm userUpdateForm,
        BindingResult bindingResult) 
    logger.info("UserUpdate: " + userUpdateForm);

    User updatedUser = userService.updateUser(userUpdateForm
            .createUser(userId));

    return new ResponseEntity(updatedUser, HttpStatus.OK);


@RequestMapping(method = RequestMethod.DELETE, value = "/users/userId")
public ResponseEntity deleteUser(@PathVariable Long userId,
        @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) 
    logger.info("UserDelete: " + userId);

    User requestedUser = new User(userId);
    userService.deleteUser(requestedUser);

    return new ResponseEntity(HttpStatus.NO_CONTENT);

以下是控制器测试代码

我不知道如何,但第二个请求 testDeleteUser() 具有会话值,并且它是使用先前测试的同一用户。所以即使在 deleteUser() 开始时认为验证访问令牌并加载正确的新用户,但在 userDetailsImpl 的实际值不知何故在 testUpdateUser() 开始时创建了错误的用户。

@Before
public void setup() 
    mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilters(filterChainProxy).build();


@Test
public void testUpdateUser() throws Exception 
    User savedUser = signUpUser();

    // @formatter:off
    mockMvc.perform(
            put("/users/" + savedUser.getId())
            .header(HeaderUtil.AUTHORIZATION, getAuthorizationWithAccessToken())
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(UserUpdateFormFactory.newInstance())))
            .andExpect(status().isOk())
            .andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8))
            .andExpect(jsonPath("$.id", is(greaterThan(NumberUtils.INTEGER_ZERO))))
            .andExpect(jsonPath("$.name", is(equalTo(StringUtil.NEW + UserFactory.NAME))));
    // @formatter:on


@Test
public void testDeleteUser() throws Exception 
    User savedUser = signUpUser();
    String authorization = getAuthorizationWithAccessToken();

    // @formatter:off
    mockMvc.perform(
            delete("/users/" + savedUser.getId())
            .header(HeaderUtil.AUTHORIZATION, authorization)
            .contentType(TestUtil.APPLICATION_JSON_UTF8))
            .andDo(print())
            .andExpect(status().isNoContent());
    // @formatter:on

这是 UserDetailService 实现,当涉及到 loadUserByUserName() 来验证访问令牌时,它会从数据库中加载正确的用户并返回刚刚在每个测试方法开始时创建的新用户(signUpUser())。

@Service
public class UserDetailsServiceImpl implements UserDetailsService 

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String email)
            throws UsernameNotFoundException 

        User requestedUser = new User();
        requestedUser.setEmail(email);

        User savedUser = userService.findByEmail(requestedUser);

        return new UserDetailsImpl(savedUser);
    

我尝试禁用失败的会话,对我来说配置和测试代码似乎很好。 spring-security-oauth 有什么好的实际例子吗?


更新

    据我了解,mockMvc 会清除所有设置,并且每次都会使用 setUp() 方法创建很多新的模拟服务器。因此,每次都应该清除访问令牌存储,但令牌存储会以某种方式维护经过身份验证的令牌。

    在测试期间询问使用“/oauth/token”请求的访问令牌,下面是调用 InMemoryTokenStore 的方式。


测试过程日志

    testUpdateUser() -> POST: /oauth/token -> 存储令牌 代币:50b10897-9e15-4859-aeb0-43d0802ba42c 用户:id=2

    testUpdateUser() -> PUT: /users/2 -> 读取令牌 代币:50b10897-9e15-4859-aeb0-43d0802ba42c 用户:id=2

    testUpdateUserWithWrongUserId() -> GET: /oauth/token -> 存储令牌 令牌:50b10897-9e15-4859-aeb0-43d0802ba42c -> 已存在于令牌中 用户:id=2 -> id=4:用户已更新为新用户

    testUpdateUserWithWrongUserId() -> PUT: /users/0 -> 读取令牌 代币:50b10897-9e15-4859-aeb0-43d0802ba42c 用户:id=2

    testDeleteUser() -> GET: /oauth/token -> 没有存储令牌,应该存储令牌

    testDeleteUser() -> 删除:/users/5 -> 读取令牌 代币:50b10897-9e15-4859-aeb0-43d0802ba42c user: id=2 -> 用户应该是 id=5,它是用 userSignUp() 创建的


问题 如何用 mockMvc 清除 InMemoryTokenStore 每个测试方法?

【问题讨论】:

您只使用一个 wac (WebApplicationContext) 实例,因此主体在两次测试执行之间存储在那里。为什么不在像delete("/users/" + savedUser.getId()).principal(null) 这样的每个测试中明确设置主体?? AuthenticationPrincipal 旨在成为 SecurityContextHolder.getContext().getAuthentication().getPrincipal(),因此这是预期的行为。我想我很难弄清楚你想让你的代码做什么。 @RobWinch 感谢您的评论。我想以干净的状态测试每个方法,以便我希望每个测试方法都没有会话或主体,并且我理解每个测试方法都应该是干净的,以防止任何其他奇怪的行为。 @gsolecki 感谢您的指导,但是,principal() 不会为 null,而且很难找出我应该如何调用 principal(),请给我更具体的例子吗? @RobWinch 我认为 spring-security-oauth 应该只依赖于每个测试的请求标头中的 accessToken 。如果我在测试代码上做错了,请让我指导正确的测试。 【参考方案1】:
    // ignore spring security session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);

【讨论】:

也许解释你必须把这段代码放在哪里并解释为什么它应该解决问题会让你的答案质量更好。 您需要扩展 WebSecurityConfigurerAdapter 并将 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER) 放在那里。这源于休息服务是无状态的并且会话保持状态的论点,但是因为会话中保持的状态与数据无关,而是会话是关心的元数据。

以上是关于@AuthenticationPrincipal 对象返回会话值的主要内容,如果未能解决你的问题,请参考以下文章

@AuthenticationPrincipal 是如何工作的?

@AuthenticationPrincipal 与 Spring Boot 不工作

@AuthenticationPrincipal 返回空用户

使用 @AuthenticationPrincipal 注入自定义 OidcUser 包装器

使用 EnableWebSecurity 时 AuthenticationPrincipal 为空

AuthenticationPrincipal 返回空的 UserDetails 对象