【问题标题】:Spring Security @AuthenticationPrincipalSpring Security @AuthenticationPrincipal
【发布时间】:2016-02-10 12:39:36
【问题描述】:

我一直在尝试让 @AuthenticationPrincipal 与自定义 User 类一起正常工作。不幸的是,用户始终为空。代码如下:

控制器

@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(@AuthenticationPrincipal User user) {
    ModelAndView mav= new ModelAndView("/web/index");
    mav.addObject("user", user);
    return mav;
}

安全配置

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomUserDetailsService customUserDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
    }

}

CustomUserDetailsS​​ervice

@Component
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // Spring Data findByXY function
    return userRepository.findByUsername(username);
}

用户实体

public class User implements UserDetails{
    private String username;
    private String password;
    private Collection<Authority> authorities;

    // Getters and Setters

}

权威实体

public class Authority implements GrantedAuthority{
    private User user;
    private String role;

    // Getters and Setters

    @Override
    public String getAuthority() {
        return this.getRole();
    }
}

我已经尝试了各种我在网上找到的解决方案,例如像这样转换我的自定义用户对象:

return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), true, true, true, true,  authorities);

获得活跃用户的其他方法都没有问题,但我发现@AuthenticationProvider CustomUserObject 是最干净的方法,这就是我想让它工作的原因。非常感谢任何帮助。

【问题讨论】:

  • 嗯.. 你总是可以获取Principal 对象,获取主体名称并通过用户名查找用户。如果春天秒。当然,身份验证正在正确实施。
  • 是的,这就是我现在正在做的事情...... Authentication auth = SecurityContextHolder.getContext().getAuthentication();用户主体 = uRep.findByUsername(auth.getName()); .. 但正如我所说,我希望有可用的干净解决方案
  • 我面临与您完全相同的问题。 @ AuthenticationPrincipal 总是给我一个默认用户(刚刚使用默认构造函数初始化的 User 类),而 ContextSecurityHolder 和 HttpServletRequest#getUserPrincipal() 给了我预期的(经过身份验证的)用户。顺便说一句,你不应该用@EnableWebSecurity而不是@EnableWebMvcSecurity(在Spring Security 4+中已弃用)来注释你的SecurityConfig吗?
  • 感谢您指出这一点,完全错过了:)。如果你弄明白了,请不要犹豫在这里发帖。
  • 我在 Spring 提交了 JIRA:jira.spring.io/browse/SEC-3145

标签: java spring spring-security


【解决方案1】:

您可以直接在方法参数中指定对经过身份验证的用户的依赖,而不是使用 @AuthenticationPrincipal。如下所示

@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(Principal user) {
    ModelAndView mav= new ModelAndView("/web/index");
    mav.addObject("user", user);
    return mav;
} 

此 Principal 对象将是通过 Spring Security 进行身份验证的实际对象。当方法被调用时,Spring 会为你注入这个。

【讨论】:

  • 正如我所说,这些方法都可以正常工作。但是根据文档,只要我的用户对象实现了 UserDetails 并且我启用了 WebMvcSecurity (我使用的是 Spring Security 4.0.2),我应该能够通过 AuthenticationPrincipal 注释获取我的自定义用户对象。知道我可能会错过什么吗?
  • 令人惊讶的是,@AuthenticationPrincipal User 的方法对我不起作用,但使用Principal 直接有效。在这一点上等待Spring 3145 ticket的答复...
  • @RémiDoolaeghe - 这是一个较晚的答案,但您需要获得UserDetails 的实例,而不仅仅是User
【解决方案2】:

我找到了另一种解决方案,即使这不是规范的解决方案。

在您的控制器中

@RequestMapping(value = "/login", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<UserDTO> login(@RequestBody UserDTO user){
    try {
        usersService.authenticate(user.getUsername(), user.getPassword());
        return new ResponseEntity<UserDTO>(user, HttpStatus.OK);
    }
    catch (BadCredentialsException e){
        return new ResponseEntity<UserDTO>(HttpStatus.UNAUTHORIZED);
    }
}

UserDTO os 只是一个包含用户名和密码的表单

在您的 CustomUserDetailsS​​ervice 中

public void authenticate(String username, String password){
    try{
        User user = new User(username, password);
        Authentication request = new UsernamePasswordAuthenticationToken(user, password, Arrays.asList(WebSecurityConfiguration.USER));
        Authentication result = authenticationManager.authenticate(request);
        SecurityContextHolder.getContext().setAuthentication(result);       
    } catch (InternalAuthenticationServiceException e){
        // treat as a bad credential
    }
}

实现你自己的 AuthenticationManager

@Component
class DefaultAuthenticationManager implements AuthenticationManager {

@Autowired
private CustomUserDetailsService usersService;

public Authentication authenticate(Authentication auth) throws AuthenticationException {
    UserDetails user = usersService.loadUserByUsername(((User)auth.getPrincipal()).getUsername());
    if (user != null) {
        return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
    }
// Handle bad credentials here  
}
}

最根本的是CustomUserDetailsService#authenticate中的Principal是一个对象,而不是你认证用户的名字,这样框架就可以处理它,然后通过@AuthenticationPrincipal机制注入。这对我有用。

【讨论】:

    【解决方案3】:
    @RequestMapping(method= RequestMethod.GET,value="/authenticate", produces = MediaType.APPLICATION_JSON_VALUE)
    public Object authenticate(@AuthenticationPrincipal Object obj) {
        return obj;
    }
    

    我有同样的问题,我能够做到这一点。 您可以将其与映射器一起使用

    @RequestMapping(method = RequestMethod.GET, value = "/authenticate2", produces = MediaType.APPLICATION_JSON_VALUE)
    public User authenticate2(@AuthenticationPrincipal Object obj) throws IOException {
        return mapper.readValue(mapper.writeValueAsString(obj), User.class);
    }
    

    这些对我有用,我希望将来对任何人都有用

    【讨论】:

      【解决方案4】:

      我面临的问题与您完全相同。 由于某种原因,spring security 的@EnableWebSecurity 没有自动添加argumentResolver,需要手动添加:

      <mvc:annotation-driven>
          <mvc:argument-resolvers>
              <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver"/>
          </mvc:argument-resolvers>
      </mvc:annotation-driven>
      

      【讨论】:

        【解决方案5】:

        在我的例子中,我得到一个 String 返回(用户名)而不是 UserDetails 对象,即您应该将方法签名定义为

        public ModelAndView index(@AuthenticationPrincipal String username)
        

        这并不奇怪,因为 @AuthenticationPrincipal 实际上返回 Authentication.getPrincipal() 并且根据文档:

        在使用用户名和密码的身份验证请求的情况下, 这将是用户名。预计呼叫者将填充 身份验证请求的主体。

        AuthenticationManager 实现通常会返回一个 身份验证包含更丰富的信息作为使用主体 由应用程序。许多身份验证提供程序将创建一个 UserDetails 对象作为主体。 见:https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/api/

        所以,我假设您的 AuthenticationManager 实现只返回一个用户名

        【讨论】:

          【解决方案6】:

          你需要检查你是否同时使用了正确版本的@AuthenticationPrincipal注解和正确版本的AuthenticationPrincipalArgumentResolver类。

          在 Spring Security 4.0 版本之前,您必须使用类:

          • org.springframework.security.web.bind.annotation.AuthenticationPrincipal

          • org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver

          从 4.0 版本开始,您必须使用:

          • org.springframework.security.core.annotation.AuthenticationPrincipal

          • org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver

          查看@AuthenticationPrincipal官方文档了解配置示例。

          【讨论】:

            【解决方案7】:

            我通过扩展 AbstractUserDetailsAuthenticationProvider 的身份验证提供程序来实现这一点。

            在我的提供者中,我覆盖了以下方法:

            受保护的用户详细信息检索用户( 字符串用户名, UsernamePasswordAuthenticationToken 身份验证)抛出 AuthenticationException {}

            在这个方法中,我正在创建我的 UserDetails 实现并返回它。然后我可以通过 AuthenticationPrincipal 注释在控制器中获取它。

            【讨论】:

              【解决方案8】:

              尽管文档中提到了 customUser,但我认为这是不可能的。使用@AuthenticationPrincipaljava.lang.Objectorg.springframework.security.core.userdetails.User 作为弹簧控制器方法参数类型。

              如果类型不匹配,则返回 null,除非 AuthenticationPrincipal.errorOnInvalidType() 为真,在这种情况下 将抛出 ClassCastException。 Spring API Documentation

              【讨论】:

                【解决方案9】:

                来自文档:

                类 AuthenticationPrincipalArgumentResolver 将使用解析 CustomUser 参数 来自 SecurityContextHolder 的 Authentication.getPrincipal()。如果 Authentication 或 Authentication.getPrincipal() 为空,它将 返回空值。如果类型不匹配,则返回 null,除非 AuthenticationPrincipal.errorOnInvalidType() 为真,在这种情况下 将抛出 ClassCastException。

                这个简单的代码对我有用:

                @RestController
                public class ApiController {
                
                  @RequestMapping(method = RequestMethod.GET, path = "/api/public/{id}")
                  public ResponseEntity<ApiResponse> getPublicResource(
                      @PathVariable Integer id,
                      @AuthenticationPrincipal String principal
                  ) {
                
                    if (id % 2 == 0) {
                      SecurityContext context = SecurityContextHolder.getContext();
                      Authentication authentication = context.getAuthentication();
                      return ResponseEntity.ok(new ApiResponse(
                          authentication.getPrincipal().toString(),
                          "this method scrapes principal directly from SpringSecurityContext"));
                    }
                
                    return ResponseEntity.ok(new ApiResponse(
                        principal.toString(),
                        "this method retrieves principal from method argument"));
                
                  }
                
                }
                

                只需检查身份验证对象中主体的类型即可。

                【讨论】:

                  猜你喜欢
                  • 2016-07-13
                  • 2015-10-22
                  • 2015-03-11
                  • 2016-07-12
                  • 2018-10-21
                  • 2019-08-05
                  • 1970-01-01
                  • 2016-05-11
                  • 2020-03-09
                  相关资源
                  最近更新 更多