Spring Security 入门之自定义表单登录开发实现(三)

1. 前言

在弄懂HelloWorld案例后,我们将自定义登陆页面并弄自定义一个登陆表单,然后覆盖Spring Security默认的登陆页面。首先在引入如下依赖:

    <!--引入thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
   <!--Spring boot Web容器-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <!--引入SpringSecurity依赖-->
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-security</artifactId>
      </dependency>

在配置文件中禁用引入thymeleaf 缓存如下所示:

server:
  port: 8001
spring:
  application:
    name: gsr-auth
  security:
    user:
      name: admin
      password: admin123
  ##  thymeleaf配置
  thymeleaf:
    cache: false

2. 自定义认证

2.1 自定义登录页面

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <title>login</title>
    <link href="favicon.ico" rel="shortcut icon" />
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.0/css/bootstrap.min.css" rel="stylesheet">
</head>
<body style=" background: url(https://img.zcool.cn/community/019e835c611d92a801203d22c2cb34.jpg@1280w_1l_2o_100sh.jpg) no-repeat center center fixed; background-size: 100%;">


<div class="modal-dialog" style="margin-top: 10%;">
    <div class="modal-content">
        <div class="modal-header">

            <h4 class="modal-title text-center" id="myModalLabel">自定义登录页面</h4>
        </div>
        <form id="loginForm" class="form" th:action="@{/doLogin}" method="post">
            <div class="modal-body" id = "model-body">

                <div class="form-group">
                    <input type="text" name="username" class="form-control"placeholder="请输入用户名" autocomplete="off">
                </div>
                <div class="form-group">
                    <input type="password" name="password" class="form-control" placeholder="请输入密码" autocomplete="off">
                </div>
            </div>
            <div class="modal-footer">
                <div class="form-group">
                    <input type="submit" class="form-control btn btn-info btn-md" name="submit"  value="登陆" autocomplete="off">
                </div>
            </div>
        </form>
    </div>
</div>
</body>
</html>

2.2 后端认证逻辑

表单定义好以后,接下来定义三个个如下方法:

@Controller
public class HelloWorldController {

    @GetMapping("/helloWorld")
    public ResponseEntity<String> helloWorld(){
        return ResponseEntity.ok("helloWorld");
    }

    /**
     * spring Security 自定义登陆成功跳转的URL必须为post,否则会报错405的问题()
     * @return ResponseEntity<String>
     */
    @PostMapping("/index")
    public ResponseEntity<String> index() {
        return ResponseEntity.ok("login Success!");
    }

    @RequestMapping("/login.html")
    public String login() {
        return  "login";
    }
}

最后在配置一个Spring Security的配置类:

@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().antMatchers("/index","/login.html").permitAll().anyRequest().authenticated()
                .and().formLogin().loginPage("/login.html").loginProcessingUrl("/doLogin").successForwardUrl("/index")
                .usernameParameter("username").passwordParameter("password")
                .and().csrf().disable();
    }

Spring Security 提供了一个WebSecurityConfigurerAdapter 抽象类,此类提供了丰富的配置,后面我们在深入研究此类,此处略过。上面配置类的代码简单分析一下:

  1. configure 方法可以使用链式配置。
  2. authorizeHttpRequests 表示开启权限认证。
  3. antMatchers 表示url路径匹配,此方法参数是一个可变参数列表。
  4. permitAll 结合antMatchers 表示匹配到的url路径可以直接访问(无需任何权限)
  5. anyRequest().authenticated() 表示所有的请求都需要认证才能访问。
  6. formLogin() 表示开启表单登陆配置,loginPage 指定登陆页面地址。loginProcessingUrl 用来配置登陆访问的接口地址,successForwardUrl 登陆成功后转发地址,usernameParameter 指定表单登陆的用户名属性,passwordParameter 指定表单中登陆的密码属性。需要注意的是loginProcessingUrl、usernameParameter、passwordParameter这三个属性的值需要与自定义登陆页表单相关属性一致。
  7. csrf().disable()表示禁用csrf 安全防御机制。

配置完成后,浏览器中直接输入:http://127.0.0.1:8001/ 会自动跳转到登陆页面:
在这里插入图片描述
输入用户名和密码,登陆成功跳转到/index 如下所示:
在这里插入图片描述

3. 自定义登陆成功处理

在前面配置中,我们使用successForwardUrl 作为用户登陆成功后的跳转地址,除了这个方法以外,defaultSuccessUrl也可以作为用户跳转地址。两者的区别如下所示:

  1. defaultSuccessUrl 表示用户登陆成功后会自动重定向到登陆之前的地址上,如果在浏览器直接访问登陆页面(login.html)登陆成功后就会重定向到defaultSuccessUrl指定的页面中,但是如果在未认证的情况下直接访问/helloWorld,此时会自动重定向到登陆页面,当认证成功后就会自动重定向到/helloWorld。
  2. successForwardUrl 表示只要认证成功就会访问指定的地址而不会考虑之前访问的地址。
  3. defaultSuccessUrl 还有一个重载的方法,第二个参数传入true则作用与successForwardUrl一样,不会考虑认证前访问的地址,只要认证成功就会访问指定的地址。
  4. successForwardUrl是通过转发实现页面跳转,而defaultSuccessUrl是通过重定向实现页面跳转。

3.1 登陆成功原理

需要说明的是:无论是successForwardUrl还是defaultSuccessUrl都是通过配置AuthenticationSuccessHandler接口实现的。此接口定义如下:
在这里插入图片描述
我们可以看到AuthenticationSuccessHandler接口提供了一个默认方法,此方法从5.2后加入此接口中,这个方法专门为Authentication Filter 使用。另一个方法也是下面我们需要理解并需要使用的方法,此方法中封装了request与response,还有一个保存了登录成功的用户信息,AuthenticationSuccessHandler 一共有三个实现类如下所示:
在这里插入图片描述
其中 defaultSuccessUrl 功能实现是由SavedRequestAwareAuthenticationSuccessHandler完成的,此类定义如下:
在这里插入图片描述
可以看到onAuthenticationSuccess 方法 最后是通过重定向来实现页面跳转。
successForwardUrl 功能实现是通过ForwardAuthenticationSuccessHandler类完成的,此类接口定义如下:
在这里插入图片描述
这个类的onAuthenticationSuccess 最后是通过转发实现页面跳转。通过上面两个类的源码分析,我们也可以自定义登陆成功的逻辑,需要注意的是:上述两个类页面跳转并不满足目前流行的前后端分离架构场景中即,后端只需返回登陆成功的JSON数据给前端,然后由前端负责后续的逻辑处理。

3.2 自定义登陆成功响应处理

我们可以自定义类实现AuthenticationSuccessHandler接口并实现其onAuthenticationSuccess方法如下:

@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter implements AuthenticationSuccessHandler {

 private final static ObjectMapper objectMapper = new ObjectMapper();

    private final static String CONTENT_TYPE_UTF8 = "application/json;charset=utf-8";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().antMatchers("/index", "/login.html").permitAll().anyRequest().authenticated()
                .and().formLogin().loginPage("/login.html").loginProcessingUrl("/doLogin").defaultSuccessUrl("/index")
                .usernameParameter("username").passwordParameter("password").successHandler(this)
                .and().csrf().disable();
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 200);
        resp.put("msg", "登陆成功");
        resp.put("authentication", authentication);
        // 将登陆成功消息返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(resp));
    }
}

当重启应用时,并不会自动跳转指定的页面,而是返回如下JSON字符串:
在这里插入图片描述

4. 自定义登陆失败处理

默认情况下:登陆失败则直接重定向到登陆页面,而没有任何提示错误消息提醒,我们可以将登陆失败的异常信息输出到登陆页面上,首先在登陆页面添加如下内容:

  <div class="modal-header">

            <h4 class="modal-title text-center" id="errorMsh" th:text="{SPRING_SECURITY_LAST_EXCEPTION}"></h4>
        </div>

在这里插入图片描述
后端配置如下:

@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter implements AuthenticationSuccessHandler {

    private final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final static String CONTENT_TYPE_UTF8 = "application/json;charset=utf-8";


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().antMatchers("/index", "/login.html").permitAll().anyRequest().authenticated()
                .and().formLogin().loginPage("/login.html").loginProcessingUrl("/doLogin").defaultSuccessUrl("/index")
                .usernameParameter("username").passwordParameter("password").successHandler(this).failureForwardUrl("/login.html")
                .and().csrf().disable();
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 200);
        resp.put("msg", "登陆成功");
        resp.put("authentication", authentication);
        // 将登陆成功消息返回给前端
        response.getWriter().write(OBJECT_MAPPER.writeValueAsString(resp));
    }

重启应用后当登陆失败时会输出如下错误:
在这里插入图片描述
除了failureForwardUrl,还可以使用failureUrl,修改代码逻辑如下:
在这里插入图片描述
同时修改静态页面取值:
在这里插入图片描述
重新尝试错误登陆如下:
在这里插入图片描述

4.1 登陆失败原理

无论是 failureForwardUrl,还是 failureUrl,都是通过配置AuthenticationFailureHandler接口实现登陆失败逻辑处理。此接口定义如下:
在这里插入图片描述
Spring Security为此接口提供了5个实现类,如下图所示:
在这里插入图片描述

  1. SimpleUrlAuthenticationFailureHandler 默认处理的逻辑就是通过重定向到登陆页面,failureUrl 方法底层就是通过此类进行实现。
    在这里插入图片描述通过上面源码可以看到也可以设置 forwardToDestination 为true属性实现转发,其默认为false则是重定向。
  2. ForwardAuthenticationFailureHandler类非常简单,其处理逻辑即为failureForwardUrl 实现效果。
    在这里插入图片描述
    那么我们登陆错误展示的错误信息来源哪里呢?首先来到SimpleUrlAuthenticationFailureHandler#onAuthenticationFailure,这个方法中有如下代码片段:
    在这里插入图片描述
    首先说明的是:forwardToDestination 默认为false,我们可以看到当session不为空,或者allowSessionCreation(此属性默认为true),所以当认证失败的时候默认在session域对象里设置了一个属性,即:**WebAttributes.AUTHENTICATION_EXCEPTION **的值为:SPRING_SECURITY_LAST_EXCEPTION
    在这里插入图片描述
    这个值也就是我们最开始在静态页面取的表达式设置的值:
    在这里插入图片描述
    让我们再来分析ForwardAuthenticationFailureHandler类的 onAuthenticationFailure方法如下所示:
    在这里插入图片描述
    这次我们看到了其是在request域对象存储的WebAttributes.AUTHENTICATION_EXCEPTION的值。

4.2 自定义登陆失败响应处理

一般在前后端分离系统中,当认证失败时候只需后端发送认证失败结果为给前端,然后由前端实现认证失败的后续处理逻辑,这个时候我们可以自定义AuthenticationFailureHandler接口实现,我们继续改造AuthSecurityConfig类如下:

@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter implements AuthenticationSuccessHandler, AuthenticationFailureHandler {

    private final static ObjectMapper objectMapper = new ObjectMapper();

    private final static String CONTENT_TYPE_UTF8 = "application/json;charset=utf-8";


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().antMatchers("/index", "/login.html").permitAll().anyRequest().authenticated()
                .and().formLogin().loginPage("/login.html").loginProcessingUrl("/doLogin").defaultSuccessUrl("/index")
                .usernameParameter("username").passwordParameter("password").successHandler(this).failureHandler(this)
                .and().csrf().disable();
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 200);
        resp.put("msg", "登陆成功");
        resp.put("authentication", authentication);
        // 将登陆成功消息返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(resp));
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 500);
        resp.put("msg", "登陆失败:"+exception.getMessage());
        // 将登陆成功消息返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(resp));
    }
}

重启应用当再次认证失败的时候只会输出一个JSON如下所示:
在这里插入图片描述

5. 注销用户处理

5.1 注销原理

Spring Security 提供了LogoutSuccessHandler接口专门处理注销用户,此接口只有一个onLogoutSuccess
方法,接口定义如下:
在这里插入图片描述
spring Security一共提供了4个类实现此接口如下所示:
在这里插入图片描述
上文中我们配置的登出逻辑是由SimpleUrlLogoutSuccessHandler类实现的,这个类onLogoutSuccess
调用了抽象父类的方法:
在这里插入图片描述
父类相应方法如下所示,当登出成功后就重定向到指定的url。
在这里插入图片描述Spring Security 中提供了默认的注销登录页面如下:
在这里插入图片描述当点击logOut按钮后就会再次跳转到登陆页面如下:
在这里插入图片描述
这里我们使用自己开发的静态登陆页面,在配置类添加如下代码:
在这里插入图片描述

  1. logout() 方法开启了登出的配置,logoutUrl指定注销url
  2. invalidateHttpSession表示登出并使session失效,其属性默认为true。
  3. clearAuthentication 表示清除认证信息,其属性默认为true。
  4. logoutSuccessUrl 表示注销登出成功跳转的地址。
    当重启项目后登陆认证成功后,然后在浏览器输入:http://127.0.0.1:8001/logout ,注销成功后会自动跳转到login.html。
    我们还可以在配置多个登出的url,同时并制定请求url的请求类型为get或者是post,我们修改配置类如下:
    在这里插入图片描述
    此时myLogout1与myLogout2 都可以完成注销,上述注销触发页面跳转都是在后台进行控制的,并不满足前后端分离的场景中,我们可以自定义登出成功的json给前端,然后由前端去处理后续逻辑。整体配置类代码如下:
/**
 * 自定义Spring Security 配置类
 *
 * @author GalenGao
 * @version Id: AuthSecurityConfig.java, v 0.1 2022/5/17 19:09 GalenGao Exp $$
 */
@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter implements
        AuthenticationSuccessHandler, AuthenticationFailureHandler , LogoutSuccessHandler {

    private final static ObjectMapper objectMapper = new ObjectMapper();

    private final static String CONTENT_TYPE_UTF8 = "application/json;charset=utf-8";


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .antMatchers("/index", "/login.html")
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .defaultSuccessUrl("/index")
                .usernameParameter("username")
                .passwordParameter("password")
                .successHandler(this)
                .failureHandler(this)
                .and()
                .logout()
//                .logoutUrl("/logout")
                .logoutRequestMatcher(new OrRequestMatcher(new AntPathRequestMatcher("/myLogout1", HttpMethod.GET.name()),
                        new AntPathRequestMatcher("/myLogout2", HttpMethod.POST.name())))
                .logoutSuccessHandler(this)
                .invalidateHttpSession(true)
                .clearAuthentication(true)
                .logoutSuccessUrl("/login.html")
                .and()
                .csrf().disable();
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 200);
        resp.put("msg", "登陆成功");
        resp.put("authentication", authentication);
        // 将登陆成功消息返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(resp));
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 500);
        resp.put("msg", "登陆失败:" + exception.getMessage());
        // 将登陆成功消息返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(resp));
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType(CONTENT_TYPE_UTF8);
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 200);
        resp.put("msg", "注销成功");
        resp.put("authentication", authentication);
        // 将登出成功消息返回给前端
        response.getWriter().write(objectMapper.writeValueAsString(resp));
    }
}

重启项目后 再次尝试登出成功后就返回了如下JSON字符串:
在这里插入图片描述

总结

这里我们将自定义表单登录的环节已经做了一次实战,下面我们将深入学习Spring Security 是如何进行认证,尽请期待。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值