Spring Безопасность @AuthenticationPrincipal

Я пытаюсь заставить @AuthenticationPrincipal работать с пользовательским классом User. К сожалению, пользователь всегда имеет значение null. Здесь код:

контроллер

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

}

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

Пользовательский

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 является самым чистым способом, поэтому я хотел бы заставить это работать. Любая помощь приветствуется.

Ответ 1

Вместо использования @AuthenticationPrincipal вы можете напрямую указать свою зависимость для аутентифицированного пользователя в аргументе метода. что-то ниже

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

Этот Основной объект будет фактическим объектом, который получил аутентификацию через spring безопасность. spring будет вводить это для вас, когда метод будет вызван.

Ответ 2

В моем случае я получаю 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 возвращает только имя пользователя

Ответ 3

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

У меня та же проблема, я смог выполнить эту работу. Вы можете использовать его с 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);
}

Они работали для меня, я надеюсь, что это сработает для любого в будущем

Ответ 4

Вам нужно проверить, используете ли вы правильную версию аннотации @AuthenticationPrincipal и правильную версию класса AuthenticationPrincipalArgumentResolver.

До версии 4.0 Spring Безопасность вам пришлось использовать классы:

  • 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 официальные документы для примеров конфигурации.

Ответ 5

Я нашел другое решение, даже это не канонический.

В вашем контроллере

@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 имеет только форму, содержащую имя пользователя и пароль

В вашем CustomUserDetailsService

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  
}
}

Принципиальным является то, что Principal в CustomUserDetailsService#authenticate - это объект, а не имя вашего аутентифицированного пользователя, так что структура может обрабатывать его, а затем вводить через механизм @AuthenticationPrincipal. Это сработало для меня.

Ответ 6

Я столкнулся с той же проблемой, что и вы. по какой-то причине spring security @EnableWebSecurity не добавлять аргументResolver автоматически, вам нужно добавить его вручную:

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

Ответ 7

Я достигаю этого с помощью поставщика аутентификации, который расширяет AbstractUserDetailsAuthenticationProvider.

В моем провайдере я переопределяю следующий метод:

Защищенный UserDetails retrieveUser (        Строковое имя пользователя,        Имя пользователяPasswordAuthenticationToken аутентификация) создает исключение AuthenticationException {}

и в этом методе я создаю свою реализацию UserDetails и возвращаю ее. Затем я могу получить его в контроллере с аннотацией AuthenticationPrincipal.

Ответ 8

Хотя в документации упоминается о customUser, я не думаю, что это возможно. Используйте @AuthenticationPrincipal с java.lang.Object или org.springframework.security.core.userdetails.User в качестве типа параметра метода контроллера пружины.

Если типы не совпадают, возвращается ноль, если только AuthenticationPrincipal.errorOnInvalidType() имеет значение true, и в этом случае ClassCastException будет брошен. Spring API Документация