将UserDetails传递给spring控制器

时间:2016-03-14 22:53:19

标签: spring security model-view-controller controller

我正在使用spring-webmvc版本4.07和Spring Security v 3.2查看一个简单的Spring启动项目。使用以下配置类覆盖基本安全配置,以提供安全URL和自定义UserDetails实现:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private ReaderRepository readerRepository;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .antMatchers("/").access("hasRole('READER')")
        .antMatchers("/**").permitAll()
      .and()
      .formLogin()
        .loginPage("/login")
        .failureUrl("/login?error=true");
  }

  @Override
  protected void configure(
              AuthenticationManagerBuilder auth) throws Exception {
    auth
      .userDetailsService(new UserDetailsService() {
        @Override
        public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
          UserDetails userDetails = readerRepository.findOne(username);
          if (userDetails != null) {
            return userDetails;
          }
          throw new UsernameNotFoundException("User '" + username + "' not found.");
        }
      });
  }

}

上面的readerRepository.findOne(用户名)基于接口

public interface ReaderRepository extends JpaRepository<Reader, String> {

    List<Book> findByReader(String reader);

}

因此它正在使用登录页面上提供的用户名在数据库中查找Reader。 Reader类是

@Entity
public class Reader implements UserDetails {

  private static final long serialVersionUID = 1L;

  @Id
  private String username;

  private String fullname;
  private String password;

  ...Setters/Getters, getAuthorities(), isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled()

}

有一个控制器,

@Controller
@RequestMapping("/")
@ConfigurationProperties("amazon") 
public class ReadingListController {

    private ReadingListRepository readingListRepository;
  private AmazonProperties amazonConfig;

    @Autowired
    public ReadingListController(ReadingListRepository readingListRepository,
        AmazonProperties amazonConfig) {
        this.readingListRepository = readingListRepository;
    this.amazonConfig = amazonConfig;
    }

    @RequestMapping(method=RequestMethod.GET)
    public String readersBooks(Reader reader, Model model) {
        List<Book> readingList = readingListRepository.findByReader(reader);
        if (readingList != null) {
            model.addAttribute("books", readingList);
            model.addAttribute("reader", reader);
            model.addAttribute("amazonID", amazonConfig.getAssociateId());
        }
        return "readingList";
    }

    @RequestMapping(method=RequestMethod.POST)
    public String addToReadingList(Reader reader, Book book) {
        book.setReader(reader);
        readingListRepository.save(book);
        return "redirect:/";
    }

}

我使用命令“gradle bootrun”运行应用程序。当我去localhost时:8080 /我会看到一个登录页面。当我登录时,调用控制器的readerBooks(...)方法。此方法有一个Reader对象作为参数,其中包含登录的阅读器,其登录页面上输入了用户名。 Reader类(实现UserDetails)显然是由Spring传入的。但是,我从未见过这样做过。我已经看到它通过传入一个使用@AuthenticationPrincipal注释的Principal,或者通过从控制器方法中访问SecurityContext来完成,但我找不到任何记录为什么在这种情况下传入Reader的原因。它只是因为Reader实现UserDetails?

1 个答案:

答案 0 :(得分:0)

我发现了答案。这是我以前在春天没见过的东西。我忽略了一个课程,这是正在发生的事情的关键。创建了HandlerMethodArgumentResolver的自定义实现,以便Spring可以识别Reader类并像使用Model参数一样注入它:

import org.springframework.core.MethodParameter;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

@Component
public class ReaderHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return Reader.class.isAssignableFrom(parameter.getParameterType());
  }

  @Override
  public Object resolveArgument(MethodParameter parameter,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
      WebDataBinderFactory binderFactory) throws Exception {

    Authentication auth = (Authentication) webRequest.getUserPrincipal();
    return auth != null && auth.getPrincipal() instanceof Reader ? auth.getPrincipal() : null;

  }

}

此类具有@Component,因此它将在WebConfig.java类中声明为bean:

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/login").setViewName("login");
  }

  @Override
  public void addArgumentResolvers(
      List<HandlerMethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new ReaderHandlerMethodArgumentResolver());
  }

}

使用此代码,Spring现在将Reader识别为控制器方法的有效参数,并注入一个填充了经过身份验证的主体的Reader类(如果存在)。否则,它将使用null填充Reader。我在WebConfig.java中看到了代码,但忽略了它,因为我不知道它的用途。我仍然不太确定的是为什么HandlerMethodArgumentResolver类的supportsParameter方法不像

那样简单
@Override
 public boolean supportsParameter(MethodParameter parameter) {
    return parameter.getParameterType().equals(Reader.class);
 }