英文:
My Spring-Boot custom login form isn't working [UPDATED]
问题
以下是UserAccountController.java
的翻译部分:
@RestController
@Controller
public class UserAccountController {
@Autowired
private CustomerRepository userRepository;
@Autowired
private ConfirmationTokenRepository confirmationTokenRepository;
@Autowired
private EmailSenderService emailSenderService;
@RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView displayRegistration(ModelAndView modelAndView, Customer user) {
// ... (省略部分代码)
return modelAndView;
}
@RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView registerUser(ModelAndView modelAndView, Customer user) {
// ... (省略部分代码)
return modelAndView;
}
@RequestMapping(value = "/confirm-account", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView confirmUserAccount(ModelAndView modelAndView, @RequestParam("token") String confirmationToken) {
// ... (省略部分代码)
return modelAndView;
}
@RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST})
public ModelAndView login(ModelAndView modelAndView, @RequestParam("emailID") String email, @RequestParam("password") String password) {
// ... (省略部分代码)
return modelAndView;
}
@RequestMapping(value = "/customerDetails", method = RequestMethod.GET)
public ModelAndView displayCustomerList(ModelAndView modelAndView) {
// ... (省略部分代码)
return modelAndView;
}
// getters and setters...
}
这里只翻译了UserAccountController.java
的部分代码。如果需要其他代码的翻译,请继续提供。
英文:
I'm new to Spring-Boot and currently, I'm developing a custom login form with a MySQL database connection.
So I have already developed the registration function and it works fine.
But when I try to log in to an account, it always showing "Invalid username and password."
I'm using Eclipse IDE.
Below is the Controller class: WebMvcConfiguration.java
@ComponentScan("org.springframework.security.samples.mvc")
@Controller
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/customerAccount")
public String authenticate() {
// authentication logic here
return "customerAccount";
}
@GetMapping("/adminDashboard")
public String adminDashboard() {
return "adminDashboard";
}
@GetMapping("/Category")
public String Category() {
return "Category";
}
@GetMapping("/Index")
public String Index() {
return "Index";
}
@PostMapping("/RatingAccount")
public String RatingAccount() {
return "RatingAccount";
}
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/static/**").addResourceLocations("/resources/static/");
}
}
Below is the UserAccountController.java
@RestController
@Controller
public class UserAccountController {
@Autowired
private CustomerRepository userRepository;
@Autowired
private ConfirmationTokenRepository confirmationTokenRepository;
@Autowired
private EmailSenderService emailSenderService;
@RequestMapping(value="/register", method = RequestMethod.GET)
public ModelAndView displayRegistration(ModelAndView modelAndView, Customer user)
{
modelAndView.addObject("user", user);
modelAndView.setViewName("register");
return modelAndView;
}
@RequestMapping(value="/register", method = RequestMethod.POST)
public ModelAndView registerUser(ModelAndView modelAndView, Customer user)
{
Customer existingUser = userRepository.findByEmailIdIgnoreCase(user.getEmailId());
if(existingUser != null)
{
modelAndView.addObject("message","This email already exists!");
modelAndView.setViewName("error");
}
else
{
userRepository.save(user);
ConfirmationToken confirmationToken = new ConfirmationToken(user);
confirmationTokenRepository.save(confirmationToken);
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(user.getEmailId());
mailMessage.setSubject("Complete Registration!");
mailMessage.setFrom("rukshan033@gmail.com");
mailMessage.setText("To confirm your account, please click here : "
+"http://localhost:8082/confirm-account?token="+confirmationToken.getConfirmationToken());
emailSenderService.sendEmail(mailMessage);
modelAndView.addObject("emailId", user.getEmailId());
modelAndView.setViewName("successfulRegisteration");
}
return modelAndView;
}
@RequestMapping(value="/confirm-account", method= {RequestMethod.GET, RequestMethod.POST})
public ModelAndView confirmUserAccount(ModelAndView modelAndView, @RequestParam("token")String confirmationToken)
{
ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken(confirmationToken);
if(token != null)
{
Customer user = token.getCustomer();
//Customer user = userRepository.findByEmailIdIgnoreCase(token.getCustomer().getEmailId());
user.setEnabled(true);
userRepository.save(user);
modelAndView.setViewName("accountVerified");
}
else
{
modelAndView.addObject("message","The link is invalid or broken!");
modelAndView.setViewName("error");
}
return modelAndView;
}
@RequestMapping(value="/login", method= {RequestMethod.GET, RequestMethod.POST})
// @ResponseBody
public ModelAndView login(ModelAndView modelAndView, @RequestParam("emailID")String email, @RequestParam("password")String password)
{
Customer user = userRepository.findByEmailIdIgnoreCase(email);
if(user == null) {
modelAndView.addObject("message1","Invalid E-mail. Please try again.");
modelAndView.setViewName("login");
}
else if (user != null && user.getPassword()!=password) {
modelAndView.addObject("message1","Incorrect password. Please try again.");
modelAndView.setViewName("login");
}
else if (user != null && user.getPassword()==password && user.isEnabled()==false) {
modelAndView.addObject("message1","E-mail is not verified. Check your inbox for the e=mail with a verification link.");
modelAndView.setViewName("login");
}
else if (user != null && user.getPassword()==password && user.isEnabled()==true) {
modelAndView.addObject("message1","Welcome! You are logged in.");
modelAndView.setViewName("customerAccount");
}
return modelAndView;
}
@RequestMapping(value="/customerDetails", method = RequestMethod.GET)
public ModelAndView displayCustomerList(ModelAndView modelAndView)
{
modelAndView.addObject("customerList", userRepository.findAll());
modelAndView.setViewName("customerDetails");
return modelAndView;
}
// getters and setters
public CustomerRepository getUserRepository() {
return userRepository;
}
public void setUserRepository(CustomerRepository userRepository) {
this.userRepository = userRepository;
}
public ConfirmationTokenRepository getConfirmationTokenRepository() {
return confirmationTokenRepository;
}
public void setConfirmationTokenRepository(ConfirmationTokenRepository confirmationTokenRepository) {
this.confirmationTokenRepository = confirmationTokenRepository;
}
public EmailSenderService getEmailSenderService() {
return emailSenderService;
}
public void setEmailSenderService(EmailSenderService emailSenderService) {
this.emailSenderService = emailSenderService;
}
}
Below is the Security Configuration class: SecurityConfig.java
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(
"/Index/**"
,"/Category/**"
,"/register**"
,"/css/**"
,"/fonts/**"
,"/icon-fonts/**"
,"/images/**"
,"/img/**"
,"/js/**"
,"/Source/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
Below is the Thymeleaf login page: login.html
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
<head>
<title tiles:fragment="title">Login</title>
</head>
<body>
<div tiles:fragment="content">
<form name="f" th:action="@{/login}" method="post">
<fieldset>
<legend>Please Login</legend>
<div th:if="${param.error}" class="alert alert-error">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-success">
You have been logged out.
</div>
<label for="emailId">E-mail</label>
<input type="text" id="emailId" name="emailId"/>
<label for="password">Password</label>
<input type="password" id="password" name="password"/>
<div class="form-actions">
<button type="submit" class="btn">Log in</button>
</div>
</fieldset>
</form>
</div>
</body>
</html>
<!-- end snippet -->
Below is the page which I should be redirect to: customerAccount.html
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-html -->
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Welcome</title>
</head>
<body>
<form th:action="@{/customerAccount}" method="post">
<center>
<h3 th:inline="text">Welcome [[${#httpServletRequest.remoteUser}]]</h3>
</center>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Logout" />
</form>
</form>
</body>
</html>
<!-- end snippet -->
EDIT
New UserDetailsService Class:
public class CustomerDetailsService implements UserDetailsService{
@Autowired
private CustomerRepository customerRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Customer customer = customerRepository.findByEmailIdIgnoreCase(username);
if (customer == null) {
throw new UsernameNotFoundException(username);
}
return new MyUserPrincipal(customer);
}
}
class MyUserPrincipal implements UserDetails {
private Customer customer;
public MyUserPrincipal(Customer customer) {
this.customer = customer;
}
@SuppressWarnings("unchecked")
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return (Collection<GrantedAuthority>) auth.getAuthorities();
}
return null;
}
@Override
public String getPassword() {
return customer.getPassword();
}
@Override
public String getUsername() {
return customer.getEmailId();
}
@Override
public boolean isAccountNonExpired() {
return false;
}
@Override
public boolean isAccountNonLocked() {
return false;
}
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@Override
public boolean isEnabled() {
return customer.isEnabled();
}
}
I added some System.out.print()
s and found out my UserAccountController
isn't getting accessed. The CustomerDetailsService
class is also accessed and the username is passing correctly. How do I connect the controller with this?
答案1
得分: 2
我建议使用Spring Security。由于这是您每次创建新应用程序时都会复制的代码,最好将其清理并让框架来处理。
首先,您的WebSecurityConfigurerAdapter
的实现如下:
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String USER_BY_USERNAME_QUERY = "select Email, Wachtwoord, Activatie from users where Email = ?";
private static final String AUTHORITIES_BY_USERNAME_QUERY = "select Email, Toegang from users where Email = ?";
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private PasswordEncoder encoder;
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/public/**", "/css/**", "/js/**", "/font/**", "/img/**", "/scss/**", "/error/**").permitAll()
.antMatchers("/mymt/**").hasAnyRole("MEMBER", "ADMIN", "GUEST", "BOARD")
.antMatchers("/admin/**").hasAnyRole("ADMIN", "BOARD")
.antMatchers("/support/**").hasAnyRole("ADMIN", "BOARD")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.passwordEncoder(encoder)
.dataSource(dataSource)
.usersByUsernameQuery(USER_BY_USERNAME_QUERY)
.authoritiesByUsernameQuery(AUTHORITIES_BY_USERNAME_QUERY);
}
}
首先,我加载了在我的主应用程序中定义的@Bean
方法中的PasswordEncoder
。我使用BCryptPasswordEncoder
进行密码哈希。
我从Spring Boot Starter的内存中加载了我的DataSource
。这个数据源配置为我默认使用的数据库(我的默认持久性单元,因为我使用Spring Data JPA)。
对于授权的配置,首先禁用了跨站点引用,作为一项安全措施。然后,我配置了所有的路径,并告诉Spring谁可以访问Web应用程序的哪个部分。在我的数据库中,这些角色写为ROLE_MEMBER
、ROLE_BOARD
等。Spring Security会自动删除ROLE_
,但需要保留它。
之后,我添加了formLogin
并将其指向URL /login
,允许所有角色访问登录页面。我还添加了一些注销功能和异常处理。如果您愿意,可以省略这些。
然后,我配置了身份验证。在这里,我使用jdbcAuthentication
通过数据库登录。我提供了密码编码器、数据源以及两个预编译的查询,用于获取用户信息和用户角色。
这就是全部。
我的控制器非常简单:
@Controller
@RequestMapping("/login")
public class BasicLoginController {
private static final String LOGON = "authentication/login";
@GetMapping
public String showLogin() {
return LOGON;
}
}
我的主类如下:
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MyMT extends SpringBootServletInitializer {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(MyMT.class, args);
}
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public Logger logger() {
return Logger.getLogger("main");
}
}
我的HTML页面如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>MyMT Login</title>
<div th:insert="fragments/header :: header-css" />
</head>
<body>
<div class="bgMask">
<div th:insert="fragments/header :: nav-header (active='Home')" />
<main>
<div class="container">
<h1>Login</h1>
<!-- Form -->
<form class="text-center" style="color: #757575;" method="post">
<p>Om toegang te krijgen tot het besloten gedeelte moet je een geldige login voorzien.</p>
<div class="row" th:if="${param.error}">
<div class="col-md-3"></div>
<div class=" col-md-6 alert alert-danger custom-alert">
Invalid username and/or password.
</div>
<div class="col-md-3"></div>
</div>
<div class="row" th:if="${param.logout}">
<div class="col-md-3"></div>
<div class="col-md-6 alert alert-info custom-alert">
You have been logged out.
</div>
<div class="col-md-3"></div>
</div>
<!-- Name -->
<div class="md-form mt-3 row">
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="text" id="username" class="form-control" name="username" required>
<label for="username">Email</label>
</div>
<div class="col-md-3"></div>
</div>
<!-- E-mail -->
<div class="md-form row">
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="password" id="password" class="form-control" name="password" required>
<label for="password">Password</label>
</div>
<div class="col-md-3"></div>
</div>
<!-- Sign in button -->
<input type="submit" value="Login" name="login" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" />
</form>
<!-- Form -->
</div>
</main>
<div th:insert="fragments/footer :: footer-scripts" />
<div th:insert="fragments/footer :: footer-impl" />
</div>
</body>
</html>
在这里,您可以看到带有用户名和密码名称的两个输入字段。
这就是登录配置的全部内容。现在,您可以在不必为每个控制器添加安全性的情况下使用所有控制器。
从干净代码的角度来看,到处都
英文:
What I would recommend is using Spring Security. Seeing this is code you will be copying everytimee you make a new application, it's better to clean it up and let the framework duo its work.
First of all, your implementation of WebSecurityConfigurerAdapter
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String USER_BY_USERNAME_QUERY = "select Email, Wachtwoord, Activatie from users where Email = ?" ;
private static final String AUTHORITIES_BY_USERNAME_QUERY = "select Email, Toegang from users where Email = ?" ;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private PasswordEncoder encoder;
@Autowired
private DataSource dataSource;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/public/**", "/css/**", "/js/**", "/font/**", "/img/**", "/scss/**", "/error/**").permitAll()
.antMatchers("/mymt/**").hasAnyRole("MEMBER", "ADMIN", "GUEST", "BOARD")
.antMatchers("/admin/**").hasAnyRole("ADMIN", "BOARD")
.antMatchers("/support/**").hasAnyRole("ADMIN", "BOARD")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler)
;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.passwordEncoder(encoder)
.dataSource(dataSource)
.usersByUsernameQuery(USER_BY_USERNAME_QUERY)
.authoritiesByUsernameQuery(AUTHORITIES_BY_USERNAME_QUERY)
;
}
}
First of all I load in a PasswordEncoder
that I defined in my main app as a @Bean
method. I use the BCryptPasswordEncoder
for password hashing.
I load my DataSource
from the memory of my Spring Boot Starter. This one is configured for the database I use as default (my default persistence unit, because I use Spring Data jpa)
For the configuration of the authorization, I firstly disable the Cross Site Referencing, as a safety measure. Then I configure all my paths and tell Spring who can acces, what part of the web-app. In my Database, these roles are written down as ROLE_MEMBER
, ROLE_BOARD
, ... Spring Security drops the ROLE_
by itself but needs it to be there.
After that I add the formLogin and point it towards the url of /login
and permit all roles to acces the login page.
I also add some logout functionality and exception handling. You can leave these out if you want.
Then I configure the Authentication.
Here I use jdbcAuthentication
to login via Database.
I give the encoder for the password, the source of my data, and then use the two queries I precompiled that give the user information and the user roles.
That's actually it.
My Controller is very Simple
@Controller
@RequestMapping("/login")
public class BasicLoginController {
private static final String LOGON = "authentication/login";
@GetMapping
public String showLogin() {
return LOGON;
}
}
My Main looks like this:
@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MyMT extends SpringBootServletInitializer {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(MyMT.class, args);
}
@Bean
public BCryptPasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
@Bean
public Logger logger() {
return Logger.getLogger("main");
}
}
My HTML page looks like this:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>MyMT Login</title>
<div th:insert="fragments/header :: header-css" />
</head>
<body>
<div class="bgMask">
<div th:insert="fragments/header :: nav-header (active='Home')" />
<main>
<div class="container">
<h1>Login</h1>
<!-- Form -->
<form class="text-center" style="color: #757575;" method="post">
<p>Om toegang te krijgen tot het besloten gedeelte moet je een geldige login voorzien.</p>
<div class="row" th:if="${param.error}">
<div class="col-md-3"></div>
<div class=" col-md-6 alert alert-danger custom-alert">
Invalid username and/or password.
</div>
<div class="col-md-3"></div>
</div>
<div class="row" th:if="${param.logout}">
<div class="col-md-3"></div>
<div class="col-md-6 alert alert-info custom-alert">
You have been logged out.
</div>
<div class="col-md-3"></div>
</div>
<!-- Name -->
<div class="md-form mt-3 row">
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="text" id="username" class="form-control" name="username" required>
<label for="username">Email</label>
</div>
<div class="col-md-3"></div>
</div>
<!-- E-mai -->
<div class="md-form row">
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="password" id="password" class="form-control" name="password" required>
<label for="password">Password</label>
</div>
<div class="col-md-3"></div>
</div>
<!-- Sign in button -->
<input type="submit" value="Login" name="login" class="btn btn-outline-info btn-rounded btn-block z-depth-0 my-4 waves-effect" />
</form>
<!-- Form -->
</div>
</main>
<div th:insert="fragments/footer :: footer-scripts" />
<div th:insert="fragments/footer :: footer-impl" />
</div>
</body>
</html>
Here you can see the two input fields with the username and password name.
Thats it for login configuration. You can now use all your controllers without having to add security to it.
In clean code terms. Adding security everywhere is a cross cutting concern. Spring security fixes this by using Aspect Oriented Programming tricks. In other words. It intercepts the HttpRequest and first checks the user. Security automatically starts a session with user information and checks against this session.
I hope the short explanation helped to get your login working.
答案2
得分: 1
.loginPage("/login")
您在Spring Security中声明了login
路径,因此Spring Security将拦截您对该路径的调用,并且不会进入您的控制器。
因此,您的控制器,无论是
public ModelAndView login(ModelAndView modelAndView, @RequestParam("emailID") String email, @RequestParam("password") String password)
还是
@GetMapping("/login")
public String login() {
return "login";
}
都无法正常工作。
您需要提供自己的身份验证提供程序,类似于这个。或者您可以使用默认提供程序并提供自己的UserDetailsService
实现,类似于这个。
英文:
.loginPage("/login")
You have declare login
path in your Spring Security, so Spring security will intercept your call to that path, and it will never enter your controller.
So your controller, neither
public ModelAndView login(ModelAndView modelAndView, @RequestParam("emailID")String email, @RequestParam("password")String password)
or
@GetMapping("/login")
public String login() {
return "login";
}
will work.
You need to supply your own Authentication Provider, like this. Or you can use default provider and supply your UserDetailsService
implementation, like this.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论