Spring自定义登录页始终将用户标记为匿名

Spring自定义登录页始终将用户标记为匿名,spring,spring-security,Spring,Spring Security,我正在尝试创建一个自定义JSP登录页面来使用Spring安全性。 我遵循了多个示例,但在使用有效凭据按下自定义登录页面的提交按钮后,结果始终是403禁止错误,尽管用户在DB中有正确的访问权限,并且如果删除自定义登录页面,我可以使用相同的凭据成功登录 这是日志 12:03:24,923 DEBUG [io.undertow.request] (default I/O-5) Matched prefix path /FitnessTracker for path /FitnessTracker/lo

我正在尝试创建一个自定义JSP登录页面来使用Spring安全性。 我遵循了多个示例,但在使用有效凭据按下自定义登录页面的提交按钮后,结果始终是403禁止错误,尽管用户在DB中有正确的访问权限,并且如果删除自定义登录页面,我可以使用相同的凭据成功登录

这是日志

12:03:24,923 DEBUG [io.undertow.request] (default I/O-5) Matched prefix path /FitnessTracker for path /FitnessTracker/login.html
12:03:24,924 DEBUG [io.undertow.request.security] (default task-1) Attempting to authenticate /FitnessTracker/login.html, authentication required: false
12:03:24,925 DEBUG [io.undertow.request.security] (default task-1) Authentication outcome was NOT_ATTEMPTED with method io.undertow.security.impl.CachedAuthenticatedSessionMechanism@358df3b9 for /FitnessTracker/login.html
12:03:24,925 DEBUG [io.undertow.request.security] (default task-1) Authentication result was ATTEMPTED for /FitnessTracker/login.html
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
12:03:24,925 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-1) HttpSession returned null object for SPRING_SECURITY_CONTEXT
12:03:24,925 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-1) No SecurityContext was available from the HttpSession: io.undertow.servlet.spec.HttpSessionImpl@e399605d. A new one will be created.
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter'
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 5 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
12:03:24,925 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (default task-1) Request 'GET /login.html' doesn't match 'POST /logout
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 6 of 12 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
12:03:24,925 DEBUG [org.springframework.security.web.util.matcher.AntPathRequestMatcher] (default task-1) Request 'GET /login.html' doesn't match 'POST /login.html
12:03:24,925 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
12:03:24,925 DEBUG [org.springframework.security.web.savedrequest.DefaultSavedRequest] (default task-1) pathInfo: both null (property equals)
12:03:24,925 DEBUG [org.springframework.security.web.savedrequest.DefaultSavedRequest] (default task-1) queryString: both null (property equals)
12:03:24,925 DEBUG [org.springframework.security.web.savedrequest.DefaultSavedRequest] (default task-1) requestURI: arg1=/FitnessTracker/; arg2=/FitnessTracker/login.html (property not equals)
12:03:24,925 DEBUG [org.springframework.security.web.savedrequest.HttpSessionRequestCache] (default task-1) saved request doesn't match
12:03:24,926 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
12:03:24,926 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
12:03:24,926 DEBUG [org.springframework.security.web.authentication.AnonymousAuthenticationFilter] (default task-1) Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@7b207c43: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffed504: RemoteIpAddress: 127.0.0.1; SessionId: 5KhX3tAA2iFK9bLF5nkrmmyaH1EQWcSGMRCpP_5N; Granted Authorities: ROLE_ANONYMOUS'
12:03:24,927 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
12:03:24,927 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
12:03:24,927 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
12:03:24,927 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (default task-1) Secure object: FilterInvocation: URL: /login.html; Attributes: [permitAll]
12:03:24,927 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (default task-1) Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@7b207c43: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffed504: RemoteIpAddress: 127.0.0.1; SessionId: 5KhX3tAA2iFK9bLF5nkrmmyaH1EQWcSGMRCpP_5N; Granted Authorities: ROLE_ANONYMOUS
12:03:24,927 DEBUG [org.springframework.security.access.vote.AffirmativeBased] (default task-1) Voter: org.springframework.security.web.access.expression.WebExpressionVoter@7f7eaf3b, returned: 1
12:03:24,927 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (default task-1) Authorization successful
12:03:24,927 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] (default task-1) RunAsManager did not change Authentication object
12:03:24,927 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /login.html reached end of additional filter chain; proceeding with original chain
12:03:24,928 DEBUG [org.springframework.web.servlet.DispatcherServlet] (default task-1) DispatcherServlet with name 'DispatcherServlet' processing GET request for [/FitnessTracker/login.html]
12:03:24,928 DEBUG [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] (default task-1) Looking up handler method for path /login.html
12:03:24,929 DEBUG [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping] (default task-1) Returning handler method [public java.lang.String org.learning.spring.controller.LoginController.login()]
12:03:24,929 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] (default task-1) Returning cached instance of singleton bean 'loginController'
12:03:24,929 DEBUG [org.springframework.web.servlet.DispatcherServlet] (default task-1) Last-Modified value for [/FitnessTracker/login.html] is: -1
12:03:24,929 DEBUG [org.springframework.web.servlet.DispatcherServlet] (default task-1) Rendering view [org.springframework.web.servlet.view.JstlView: name 'login'; URL [/WEB-INF/jsp/login.jsp]] in DispatcherServlet with name 'DispatcherServlet'
12:03:24,929 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] (default task-1) Returning cached instance of singleton bean 'requestDataValueProcessor'
12:03:24,930 DEBUG [org.springframework.web.servlet.view.JstlView] (default task-1) Forwarding to resource [/WEB-INF/jsp/login.jsp] in InternalResourceView 'login'
12:03:24,930 DEBUG [org.apache.jasper.servlet] (default task-1) JspEngine --> /WEB-INF/jsp/login.jsp
12:03:24,930 DEBUG [org.apache.jasper.servlet] (default task-1)          ServletPath: /WEB-INF/jsp/login.jsp
12:03:24,930 DEBUG [org.apache.jasper.servlet] (default task-1)             PathInfo: null
12:03:24,930 DEBUG [org.apache.jasper.servlet] (default task-1)             RealPath: D:\Programs\Development\JAVA\Server\WildFly\wildfly-12.0.0.Final\standalone\tmp\vfs\temp\tempf4aa4a688921fb1a\FitnessTracker.war-9ab7386a6c27f7be\WEB-INF\jsp\login.jsp
12:03:24,930 DEBUG [org.apache.jasper.servlet] (default task-1)           RequestURI: /FitnessTracker/WEB-INF/jsp/login.jsp
12:03:24,930 DEBUG [org.apache.jasper.servlet] (default task-1)          QueryString: null
12:03:24,931 DEBUG [org.springframework.security.web.header.writers.HstsHeaderWriter] (default task-1) Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@6c23b3f5
12:03:24,931 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-1) SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
12:03:24,932 DEBUG [org.springframework.web.servlet.DispatcherServlet] (default task-1) Successfully completed request
12:03:24,932 DEBUG [org.springframework.security.web.access.ExceptionTranslationFilter] (default task-1) Chain processed normally
12:03:24,932 DEBUG [org.springframework.security.web.context.SecurityContextPersistenceFilter] (default task-1) SecurityContextHolder now cleared, as request processing completed
12:03:27,686 DEBUG [io.undertow.request] (default I/O-5) Matched prefix path /FitnessTracker for path /FitnessTracker/j_spring_security_check
12:03:27,687 DEBUG [io.undertow.request.security] (default task-1) Attempting to authenticate /FitnessTracker/j_spring_security_check, authentication required: false
12:03:27,687 DEBUG [io.undertow.request.security] (default task-1) Authentication outcome was NOT_ATTEMPTED with method io.undertow.security.impl.CachedAuthenticatedSessionMechanism@358df3b9 for /FitnessTracker/j_spring_security_check
12:03:27,687 DEBUG [io.undertow.request.security] (default task-1) Authentication result was ATTEMPTED for /FitnessTracker/j_spring_security_check
12:03:27,687 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /j_spring_security_check at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
12:03:27,688 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /j_spring_security_check at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
12:03:27,688 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-1) HttpSession returned null object for SPRING_SECURITY_CONTEXT
12:03:27,688 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-1) No SecurityContext was available from the HttpSession: io.undertow.servlet.spec.HttpSessionImpl@e399605d. A new one will be created.
12:03:27,688 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /j_spring_security_check at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
12:03:27,688 DEBUG [org.springframework.security.web.FilterChainProxy] (default task-1) /j_spring_security_check at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter'
12:03:27,688 DEBUG [org.springframework.security.web.csrf.CsrfFilter] (default task-1) Invalid CSRF token found for http://localhost:8080/FitnessTracker/j_spring_security_check
12:03:27,688 DEBUG [org.springframework.security.web.header.writers.HstsHeaderWriter] (default task-1) Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@6c23b3f5
12:03:27,688 DEBUG [org.springframework.security.web.context.HttpSessionSecurityContextRepository] (default task-1) SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
12:03:27,688 DEBUG [org.springframework.security.web.context.SecurityContextPersistenceFilter] (default task-1) SecurityContextHolder now cleared, as request processing completed
这是安全配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**")
                .hasRole("USER")
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
        jdbcDao.setDataSource(dataSource);
        return jdbcDao;
    }

    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
我的登录控制器

@Controller
public class LoginController {

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "login";
    }
}
登录页面

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<form action="j_spring_security_check" name="f" method="post">
    <table>
        <tr>
            <td>User:</td>
            <td><input type="text" name="j_username" value=""></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="j_password" ></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" name="Submit" value="Submit"></td>
        </tr>
    </table>
</form>
</body>
</html>
WebConfig[它不包含任何与安全性相关的内容,它只包含数据源配置、实体管理器、视图解析程序等]

@EnableWebMvc
@Configuration
@EnableTransactionManagement
@ComponentScan("org.learning.spring")
@EnableJpaRepositories("org.learning.spring.repository")
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl("jdbc:postgresql://localhost:5432/FitnessTracker");
        dataSource.setUsername("postgres");
        dataSource.setPassword("sa");

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();

        entityManagerFactoryBean.setPersistenceUnitName("punit");
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter());
        entityManagerFactoryBean.setJpaPropertyMap(getJPAPropertyMap());
        entityManagerFactoryBean.setPackagesToScan(new String[]{"org.learning.spring"});

        return entityManagerFactoryBean;
    }

    @Bean
    public HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(true);
        return hibernateJpaVendorAdapter;
    }

    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
        return jpaTransactionManager;
    }

    @Bean
    public InternalResourceViewResolver internalResourceViewResolver() {
        InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
        internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
        internalResourceViewResolver.setSuffix(".jsp");
        return internalResourceViewResolver;
    }

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        return messageSource;
    }

    @Bean
    public SessionLocaleResolver localeResolver() {
        SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
        sessionLocaleResolver.setDefaultLocale(Locale.ENGLISH);
        return sessionLocaleResolver;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("language");
        registry.addInterceptor(localeChangeInterceptor);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
        registry.addResourceHandler("/pdfs/**").addResourceLocations("/pdfs/");
    }

    private Map<String, String> getJPAPropertyMap() {
        Map<String, String> jpaPropertyMap = new HashMap<>();
        jpaPropertyMap.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL95Dialect");
        jpaPropertyMap.put("hibernate.hbm2ddl.auto", "update");
        jpaPropertyMap.put("hibernate.format_sql", "true");
        return jpaPropertyMap;
    }
}
@EnableWebMvc
@配置
@启用事务管理
@组件扫描(“org.learning.spring”)
@EnableJpaRepositories(“org.learning.spring.repository”)
公共类WebConfig实现WebMVCConfiguer{
@豆子
公共驱动器管理器数据源数据源(){
DriverManager数据源dataSource=新的DriverManager数据源();
setDriverClassName(“org.postgresql.Driver”);
setUrl(“jdbc:postgresql://localhost:5432/FitnessTracker");
dataSource.setUsername(“postgres”);
dataSource.setPassword(“sa”);
返回数据源;
}
@豆子
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean=新的LocalContainerEntityManagerFactoryBean();
setPersistenceUnitName(“punit”);
setDataSource(dataSource());
setJpaVendorAdapter(hibernateJpaVendorAdapter());
setjpapropertypap(getjpapropertypap());
entityManagerFactoryBean.setPackagesToScan(新字符串[]{“org.learning.spring”});
返回entityManagerFactoryBean;
}
@豆子
公共hibernatejbavendorapter hibernatejbavendorapter(){
hibernatejbavendorapter hibernatejbavendorapter=新的hibernatejbavendorapter();
hibernatejbavendorapter.setShowSql(true);
返回HibernateJavaEndorapter;
}
@豆子
公共JpaTransactionManager事务管理器(EntityManager工厂EntityManager工厂){
JpaTransactionManager JpaTransactionManager=新的JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
返回jpaTransactionManager;
}
@豆子
公共InternalResourceViewResolver InternalResourceViewResolver(){
InternalResourceViewResolver InternalResourceViewResolver=新的InternalResourceViewResolver();
internalResourceViewResolver.setPrefix(“/WEB-INF/jsp/”);
internalResourceViewResolver.setSuffix(“.jsp”);
返回internalResourceViewResolver;
}
@豆子
public MessageSource MessageSource(){
ResourceBundleMessageSource=新建ResourceBundleMessageSource();
messageSource.setBasename(“消息”);
返回消息源;
}
@豆子
公共会话localeResolver localeResolver(){
SessionLocaleResolver SessionLocaleResolver=新SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.ENGLISH);
返回会话LocaleResolver;
}
@凌驾
公共无效附加接收器(侦听器注册表){
LocaleChangeInterceptor LocaleChangeInterceptor=新的LocaleChangeInterceptor();
setParamName(“语言”);
addInterceptor(localeChangeInterceptor);
}
@凌驾
public void addResourceHandlers(ResourceHandlerRegistry注册表){
registry.addResourceHandler(“/assets/**”).addResourceLocations(“/assets/”);
registry.addResourceHandler(“/pdfs/**”).addResourceLocations(“/pdfs/”);
}
私有映射getjpapropertypmap(){
Map jpapropertypmap=newhashmap();
jpapropertypmap.put(“hibernate.dialogue”,“org.hibernate.dialogue.postgresql95dialogue”);
jpapropertypap.put(“hibernate.hbm2ddl.auto”,“update”);
jpaPropertyMap.put(“hibernate.format_sql”,“true”);
返回jpaPropertyMap;
}
}

由于
spring security
4.x版本默认
登录处理url
不再是
j\u spring\u security\u check
,而是
登录
。你可以办理登机手续

因此,将登录表单的操作更改为在jsp中登录:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body>
<form action="login" name="f" method="post">
    <table>
        <tr>
            <td>User:</td>
            <td><input type="text" name="j_username" value=""></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="j_password" ></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" name="Submit" value="Submit"></td>
        </tr>
    </table>
</form>
</body>
</html>
控制器应映射为“.html”后缀:

@Controller
public class LoginController {

    @RequestMapping(value = "/login.html", method = RequestMethod.GET)
    public String login() {
        return "login";
    }
}

谢谢朱米埃图和戴纳姆先生,你们两位真的救了我一天

我真正做的是让它工作

首先,我禁用了CSRF,因此我的http安全配置如下:

http
.authorizeRequests()
.antMatchers("/**")
.hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.permitAll()
.and().csrf().disable()
;
其次,我在login.jsp文件中更改了登录处理url[从j_spring_安全检查到登录]、用户名参数[从j_用户名到用户名]和密码参数[从j_密码到密码]

此外,我还使用thymeleaf作为提交表单。因此login.jsp变成了这样

<html xmlns:th="http://www.thymeleaf.org">
<body>
<form th:action="login" name="f" method="post">
    <table>
        <tr>
            <td>User:</td>
            <td><input type="text" name="username" value=""></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" name="Submit" value="Submit"></td>
        </tr>
    </table>
</form>
</body>
</html>

用户:
密码:

您实际阅读过日志记录吗?此行
12:03:27688调试[org.springframework.security.web.csrf.CsrfFilter](默认任务-1)为找到无效的csrf令牌http://localhost:8080/FitnessTracker/j_spring_security_check 
基本上说明了问题所在。您已启用CSRF保护,但在登录时未包括该保护。是的,我之前已检查并尝试禁用它,但它没有修复,因为它缺少修复的第二部分。[我在下面写下了修复它的步骤]
@Controller
public class LoginController {

    @RequestMapping(value = "/login.html", method = RequestMethod.GET)
    public String login() {
        return "login";
    }
}
http
.authorizeRequests()
.antMatchers("/**")
.hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.permitAll()
.and().csrf().disable()
;
<html xmlns:th="http://www.thymeleaf.org">
<body>
<form th:action="login" name="f" method="post">
    <table>
        <tr>
            <td>User:</td>
            <td><input type="text" name="username" value=""></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" name="Submit" value="Submit"></td>
        </tr>
    </table>
</form>
</body>
</html>