Spring Security自定义表单登录(三)

添加thymeleaf支持

application.yml

spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8

添加依赖

implementation "org.springframework.boot:spring-boot-starter-thymeleaf"
implementation "org.thymeleaf.extras:thymeleaf-extras-springsecurity5"

新增页面login.html

新增页面 /templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>自定义登录</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.0.2/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div style="margin: 40px auto; width: 320px;">
        <form action="/login" method="post">
            <div class="mb-3 row">
                <label for="username" class="visually-hidden">用户名</label>
                <input type="text" class="form-control" id="username" name="username" placeholder="请输入用户名">
            </div>
            <div class="mb-3 row">
                <label for="password" class="visually-hidden">密码</label>
                <input type="password" class="form-control" id="password" name="password" placeholder="请输入密码">
            </div>
            <div class="mb-3 row">
                <button type="submit" class="btn btn-primary mb-3">登录</button>
            </div>
        </form>
    </div>
</body>
</html>

控制器跳转
MvcConfig.java

@Configuration
public class MvcConfig implements WebMvcConfigurer {

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

}

添加配置

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests((requests) -> requests.antMatchers("/public").permitAll()
            .antMatchers(HttpMethod.GET, "/login").permitAll()
            .antMatchers("/admin").hasAnyRole("ADMIN")).cors().disable();

    http.formLogin()
            // 登录页面的路径
            .loginPage("/login");
    http.httpBasic();
}

注意:

  • csrf().disable(); csrf会拦截POST请求,需要禁用
  • .loginPage(“/login”); 设置登录页面为/login
  • antMatchers(HttpMethod.GET, “/login”).permitAll(); 登录页面不需要认证

配置认证成功失败处理器

final AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
final AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler("/login?error");

http.formLogin()
        .successHandler((request, response, authentication) -> {
            log.info("{} 登录成功", authentication.getName());
            // 跳转到认证前访问到地址(默认就是这个处理器)
            successHandler.onAuthenticationSuccess(request, response, authentication);
        })
        .failureHandler((request, response, exception) -> {
            log.error("登录失败 {}", exception.getMessage());
            // 会将exception放入session中,页面可以通过session获取异常
            failureHandler.onAuthenticationFailure(request, response, exception);
//                    response.sendRedirect("/login?error=" + exception.getMessage());
        })
        });

login.html 显示错误信息

<th:block th:if="${session.SPRING_SECURITY_LAST_EXCEPTION != null}">
    <div th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></div>
</th:block>

配置退出登录

http.formLogin()
    .logout()
    .logoutSuccessHandler((request, response, authentication) -> {
        log.info("{} 退出登录", authentication.getName());
        response.sendRedirect("/login");
    });
GET http://localhost:8080/logout

配置异常处理器

final LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");

http.authorizeRequests((requests) -> requests.antMatchers("/public").permitAll()
        .antMatchers(HttpMethod.GET, "/login").permitAll()
        .antMatchers("/admin").hasAnyRole("ADMIN")
        .anyRequest().authenticated())
        .exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
        log.warn("认证异常");
        // 默认就是LoginUrlAuthenticationEntryPoint
        loginUrlAuthenticationEntryPoint.commence(request, response, authException);
})
        .and()
        .exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
    // 403 Forbidden 没有授权,执行到此处
    log.warn("没有权限");
    HttpServletResponseUtils.write(response, "text/plain", "403 Forbidden");
});
  • authenticationEntryPoint:它在用户请求处理过程中遇到认证异常时
  • accessDeniedHandler: 没有访问权限

ExceptionTranslationFilter.java

private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
        FilterChain chain, RuntimeException exception) throws IOException, ServletException {
    if (exception instanceof AuthenticationException) {
        handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
    }
    else if (exception instanceof AccessDeniedException) {
        handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
    }
}

Github:https://github.com/jkxyx205/spring-security-learn/tree/form-configurer