Spring Boot / Spring Security,登录表单,密码检查

4

我有一个问题,可能很简单,但我不理解。

我对Spring Boot不是很熟悉,很多东西都是自动完成的。我想检查表单中输入用户名和密码的人是否在数据库中存在[并且他的账户已经被激活]。用户数据存储在application.properties中配置的MySQL数据库中。我想检查是否有提供的用户名在"user"表中,并检查提供的密码是否与数据库中的用户密码相等。目前,我可以键入来自数据库的任何用户名,密码也可以是随机的(对我来说很明显,因为我没有在任何地方检查它,而且很奇怪,因为我感觉周围的一切都表明它能够正常工作)。这对我来说听起来很简单,但我在StackOverflow或教程中找不到任何合适的解决方案。

我的总体问题是 - 我应该在哪里以及如何检查登录表单中的密码?它是否自动完成(但某种原因它不起作用),还是应该编写我的自定义控制器/服务/方法来完成?如果需要自定义控制器,则解决我的问题的方向应该是什么?

目前我不知道该去哪。我希望所有与我的问题相关的剩余代码都在这里粘贴。谢谢您提前为我的所有提示和评论。

代码:

ApplicationSecurityAdapter类:

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class ApplicationSecurityAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/user/register").permitAll()
            .antMatchers("/user/activate").permitAll()
            .antMatchers("/user/activation-send").permitAll()
            .antMatchers("/user/reset-password").permitAll()
            .antMatchers("/user/reset-password-change").permitAll()
            .antMatchers("/user/autologin").access("hasRole('ROLE_ADMIN')")
            .antMatchers("/user/delete").access("hasRole('ROLE_ADMIN')")
            .antMatchers("/img/**").permitAll()
            .antMatchers("/images/**").permitAll()
            .antMatchers("/fonts/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
            .and()
            .logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login").permitAll() // added permitAll()
            .and()
            .rememberMe().key(applicationSecret)
            .tokenValiditySeconds(31536000);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}

UserService类:

@Service
public class UserService implements UserDetailsService {

    @Value("${app.user.verification}") // set to YES
    private Boolean requireActivation;

    @Value("${app.secret}") // some random stuff
    private String applicationSecret;

    @Autowired
    private UserRepository repo;

    @Autowired
    private HttpSession httpSession;

    public final String CURRENT_USER_KEY = "CURRENT_USER";

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = repo.findOneByUserName(username);

        if(user == null) {
            throw new UsernameNotFoundException(username);
        }
        if(requireActivation && !user.getToken().equals("1")) {
            Application.log.error("User [" + username + "] tried to log in, but his account is not activated.");
            throw new UsernameNotFoundException(username + " did not activate his account.");
        }
        httpSession.setAttribute(CURRENT_USER_KEY, user);
        List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());

        return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), auth);
    }
}

用户控制器:

@Controller
// @RequestMapping("/user/*")
public class UserController {
    private Logger log = LoggerFactory.getLogger(UserController.class);

    @Value("${app.user.verification}") // YES
    private Boolean requireActivation;

    @Value("users/")
    private String userRoot;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    protected AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;

    @RequestMapping("/login")
    public String login(User user) {
        return "user/login";
    }
}

登录表单:

<div layout:fragment="content">

    <form class="form-signin" th:action="@{/login}" th:object="${user}" method="post">
        <h2 class="form-signin-heading">LOGIN PANEL</h2>
        <div class="alert alert-danger" th:if="${param.error}">
            Incorrect credentials or account not activated.
        </div>
        <input type="text" id="inputUsername" name="username" class="form-control top" placeholder="username goes here..." required="required" autofocus="autofocus"/>
        <input type="password" id="inputPassword" name="password" class="form-control bottom" placeholder="password goes here..."
           required="required"/>
        <div class="checkbox">
            <label>
                <input type="checkbox" name="remember-me"/> Remember me
            </label>
        </div>
        <button class="btn btn-lg btn-primary btn-block" type="submit">Log in</button>
    </form>
</div>
1个回答

5
问题出在你的loadUserByUsername函数中。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = repo.findOneByUserName(username);

    if(user == null) {
        throw new UsernameNotFoundException(username);
    }
    if(requireActivation && !user.getToken().equals("1")) {
        Application.log.error("User [" + username + "] tried to log in, but his account is not activated.");
        throw new UsernameNotFoundException(username + " did not activate his account.");
    }
    httpSession.setAttribute(CURRENT_USER_KEY, user);
    List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());

    return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), auth);
}

您将用户设置为会话。不要这样做!只需加载用户并返回即可。用户将自动存储在会话中,并且可以像此答案所示查找。 我认为密码检查不起作用的原因是您将BCryptPasswordEncoder配置为密码编码器。请确保您在User中存储的密码已由此编码器编码。否则,密码检查将失败。 为避免自定义激活检查,请使您的User类实现UserDetails。如果您检查文档,将检查4个标志。
boolean isAccountNonExpired() // Indicates whether the user's account has expired.
boolean isAccountNonLocked() // Indicates whether the user is locked or unlocked.
boolean isCredentialsNonExpired() // Indicates whether the user's credentials (password) has expired.
boolean isEnabled() // Indicates whether the user is enabled or disabled.

您的loadUserByUsername实现应该类似于这样。它只需要做方法名所示的事情。查找用户,如果找不到给定用户名的用户,则抛出UsernameNotFoundException异常。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  User user = repo.findOneByUserName(username);

  if(user == null) {
    throw new UsernameNotFoundException(username);
  }

  return user;
}

如果你不想让你的"用户"实现"UserDetails"(例如为了将框架和业务逻辑分离),则可以使用SpringUser,使用此构造函数,您可以设置这些标志。 你的实现可能看起来像这样:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  User user = repo.findOneByUserName(username);

  if(user == null) {
    throw new UsernameNotFoundException(username);
  }

  List<GrantedAuthority> auth = AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole());
  return new org.springframework.security.core.userdetails.User(
    user.getUserName(),
    user.getPassword(),
    requireActivation && !user.getToken().equals("1"), // enabled. Use whatever condition you like
    true, // accountNonExpired. Use whatever condition you like
    true, // credentialsNonExpired. Use whatever condition you like
    true, // accountNonLocked. Use whatever condition you like
    auth);
}

密码、权限、激活状态等内容将由Spring自动进行检查。


1
将用户设置到会话中并不是一个好主意,所以我像你的最后一段代码块一样保留了loadUserByUsername方法(因为我的“User”没有实现“UserDetails”)。幸运的是,编码正常工作。我需要处理适当的视图映射,但这与本问题无关。感谢您的帮助! - wjaroszuk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接