Java Spring Security @AuthenticationPrincipal

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33616386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:49:39  来源:igfitidea点击:

Spring Security @AuthenticationPrincipal

javaspringspring-security

提问by Lukehey

I've been trying to get @AuthenticationPrincipal to work properly with a custom User class. Unfortunately, the user is always null. Here's the code:

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

Controller

控制器

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

Security Config

安全配置

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomUserDetailsService customUserDetailsService;

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

}

}

CustomUserDetailsService

自定义用户详细信息服务

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

User Entity

用户实体

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

    // Getters and Setters

}

Authority Entity

权威实体

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

    // Getters and Setters

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

I've tried various solutions to this I found online, e.g. converting my custom user object like this:

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

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

The other ways to get the active users are working without a problem, but I find the @AuthenticationProvider CustomUserObject to be the cleanest way, which is why I would like to get this to work. Any help is greatly appreciated.

获取活跃用户的其他方法没有问题,但我发现 @AuthenticationProvider CustomUserObject 是最干净的方法,这就是我想让它工作的原因。任何帮助是极大的赞赏。

回答by Imrank

Instead of using @AuthenticationPrincipal you can directly specify your dependency for authenticated user in method argument. something as given below

您可以直接在方法参数中为经过身份验证的用户指定依赖项,而不是使用 @AuthenticationPrincipal。下面给出的东西

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

This Principal object will be actual object that got authenticated through spring security. Spring will inject this for you when the method will get invoked.

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

回答by Rémi Doolaeghe

I found another solution, even this is not the canonical one.

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

In your controller

在您的控制器中

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

Where the UserDTO os just a form to contains a username and a password

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

In your CustomUserDetailsService

在您的 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
    }
}

Implement your own AuthenticationManager

实现你自己的 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  
}
}

What is fundamental is that the Principalin the CustomUserDetailsService#authenticateis an object, not the name of your authenticated user, so that the framework can handle it and then inject through @AuthenticationPrincipalmechanism. This worked for me.

最基本的是PrincipalinCustomUserDetailsService#authenticate是一个对象,而不是您经过身份验证的用户的名称,以便框架可以处理它,然后通过@AuthenticationPrincipal机制注入。这对我有用。

回答by Ali Miskeen

@RequestMapping(method= RequestMethod.GET,value="/authenticate", produces = MediaType.APPLICATION_JSON_VALUE)
public Object authenticate(@AuthenticationPrincipal Object obj) {
    return obj;
}

I have the same problem, I was able to make this work. You could use it with a mapper

我有同样的问题,我能够完成这项工作。您可以将它与映射器一起使用

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

These worked for me, I hope it would work for anyone in the future

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

回答by yihang

I am facing exactly the same issue as you. for some reason, spring security's @EnableWebSecurity not add argumentResolver automatically, you need to add it manual:

我和你面临着完全相同的问题。出于某种原因,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>

回答by user2465039

In my case, I get a Stringback (the username) and not the UserDetailsobject, i.e. you should define the method signature as

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

public ModelAndView index(@AuthenticationPrincipal String username)

This is not strange, since @AuthenticationPrincipalin effect returns Authentication.getPrincipal()and according to the documentation:

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

In the case of an authentication request with username and password, this would be the username. Callers are expected to populate the principal for an authentication request.

The AuthenticationManager implementation will often return an Authentication containing richer information as the principal for use by the application. Many of the authentication providers will create a UserDetails object as the principal. See: https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/api/

在带有用户名和密码的身份验证请求的情况下,这将是用户名。调用者应为身份验证请求填充主体。

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

So, I am assuming that your AuthenticationManagerimplementation is returning just a username

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

回答by Matias Sebastiao

You need to check if you are using the correct version of @AuthenticationPrincipalannotation and the correct version of AuthenticationPrincipalArgumentResolverclass together.

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

Prior to 4.0 version of Spring Security you had to use classes:

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

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

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

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

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

From version 4.0 onwards, you must use:

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

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

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

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

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

Look at @AuthenticationPrincipalofficial docs for configuration examples.

查看@AuthenticationPrincipal官方文档以获取配置示例。

回答by P Sh

I'm achieving this with an authentication provider that extends AbstractUserDetailsAuthenticationProvider .

我正在使用扩展 AbstractUserDetailsAuthenticationProvider 的身份验证提供程序来实现这一点。

In my provider I'm overriding the following method:

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

protected UserDetails retrieveUser( String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {}

protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) 抛出 AuthenticationException {}

and in this method I'm creating my UserDetails implementation and return it. Then I'm able to get it in the controller with the AuthenticationPrincipal annotation.

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

回答by uiroshan

Even though documentation mentioned about a customUser, I don't think it's possible. Use @AuthenticationPrincipalwith java.lang.Objector org.springframework.security.core.userdetails.Useras spring controller method parameter type.

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

If the types do not match, null will be returned unless AuthenticationPrincipal.errorOnInvalidType() is true in which case a ClassCastException will be thrown. Spring API Documentation

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

回答by maciejlesniak

From documentation:

从文档:

Class AuthenticationPrincipalArgumentResolver will resolve the CustomUser argument using Authentication.getPrincipal() from the SecurityContextHolder. If the Authentication or Authentication.getPrincipal() is null, it will return null. If the types do not match, null will be returned unless AuthenticationPrincipal.errorOnInvalidType() is true in which case a ClassCastException will be thrown.

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

This simple code works for me:

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

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

  }

}

Just check the type of the principal in Authentication object.

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