Spring Boot之前后端分离(三):登录、登出、页面认证

前言

来啦老铁!

笔者学习Spring Boot有一段时间了,附上Spring Boot系列学习文章,欢迎取阅、赐教:

  1. 5分钟入手Spring Boot;
  2. Spring Boot数据库交互之Spring Data JPA;
  3. Spring Boot数据库交互之Mybatis;
  4. Spring Boot视图技术;
  5. Spring Boot之整合Swagger;
  6. Spring Boot之junit单元测试踩坑;
  7. 如何在Spring Boot中使用TestNG;
  8. Spring Boot之整合logback日志;
  9. Spring Boot之整合Spring Batch:批处理与任务调度;
  10. Spring Boot之整合Spring Security: 访问认证;
  11. Spring Boot之整合Spring Security: 授权管理;
  12. Spring Boot之多数据库源:极简方案;
  13. Spring Boot之使用MongoDB数据库源;
  14. Spring Boot之多线程、异步:@Async;
  15. Spring Boot之前后端分离(一):Vue前端;
  16. Spring Boot之前后端分离(二):后端、前后端集成

在前2篇文章,我们一起学习了Spring Boot之前后端分离的前端、后端、前后端集成,不过前后端集成的时候没有演示登录成功后的页面跳转。

这两天从登录后页面跳转出发,准备来个前后端交互升级版,却发现登出API没有工作正常,几经琢磨后终于解决,决定再写一篇文章,主要介绍:

1. 后端登出API的具体配置及实现;
2. JSESSIONID失效设置;
3. 登录后页面跳转、页面访问认证、登出实现;
4. 演示登录后页面跳转、页面访问认证、登出;

项目代码已更新至Git Hub仓库,欢迎取阅:

  • 前端:https://github.com/dylanz666/spring-boot-vue-frontend
  • 后端:https://github.com/dylanz666/spring-boot-vue-backend

1. 后端登出API的具体配置及实现;

我们之前直接使用Spring Security的默认登出API:/logout,感觉应该不会有问题,也没试过,可是当我真正使用它的时候却发现,翻车了???

Spring Boot之前后端分离(三):登录、登出、页面认证_第1张图片
登出翻车?

检查了一下后端config包内的WebSecurityConfig.java类,关于登出的配置如下:

...
.and()
.logout()
.permitAll()
...

后经查资料和亲自实践,发现加上logoutSuccessHandler就成功解决了,具体步骤如下:

1). domain包内创建SignOutResponse.java实体类,定义登出API的返回;
package com.github.dylanz666.domain;

import com.alibaba.fastjson.JSONArray;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.Serializable;

/**
 * @author : dylanz
 * @since : 10/10/2020
 */
@NoArgsConstructor
@AllArgsConstructor
@Data
@Component
public class SignOutResponse implements Serializable {
    private static final long serialVersionUID = 1L;

    private int code;
    private String status;
    private String message;

    @Override
    public String toString() {
        return "{" +
                "\"code\":" + code + "," +
                "\"status\":\"" + status + "\"," +
                "\"message\":\"" + message + "\"" +
                "}";
    }
}
2). 修改config包内WebSecurityConfig.java类的登出配置部分;
...
.logout()
.deleteCookies("JSESSIONID")
.logoutSuccessHandler((request, response, authentication) -> {
    response.setContentType("application/json");
    response.setStatus(200);

    SignOutResponse signOutResponse = new SignOutResponse();
    signOutResponse.setCode(200);
    signOutResponse.setStatus("success");
    signOutResponse.setMessage("success");

    PrintWriter out = response.getWriter();
    out.write(signOutResponse.toString());
    out.flush();
    out.close();
})
.permitAll()
...
这里主要做了2件事,第一个,删除cookie中的JSESSIONID,第二个,声明logoutSuccessHandler;

在验证登出API之前,我们先另外建一个需要访问认证的API,我称之为认证API,步骤如下:
1). 在controller包内创建AuthController.java类;
2). 在AuthController.java类中创建/api/auth API,代码如下:

package com.github.dylanz666.controller;

import com.alibaba.fastjson.JSONArray;
import com.github.dylanz666.domain.SignInResponse;
import com.github.dylanz666.domain.SignOutResponse;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;

/**
 * @author : dylanz
 * @since : 10/09/2020
 */
@RestController
public class AuthController {
    @GetMapping("/api/auth")
    public SignInResponse getAuth() {
        Collection authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
        UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        String username = userDetails.getUsername();

        SignInResponse signInResponse = new SignInResponse();
        signInResponse.setCode(200);
        signInResponse.setStatus("success");
        signInResponse.setMessage("success");
        signInResponse.setUsername(username);
        JSONArray userRoles = new JSONArray();
        for (GrantedAuthority authority : authorities) {
            String userRole = authority.getAuthority();
            if (!userRole.equals("")) {
                userRoles.add(userRole);
            }
        }
        signInResponse.setUserRoles(userRoles);

        return signInResponse;
    }
}

以上步骤完成后,我们再次用postman验证一下后端的登录、登出过程:

Spring Boot之前后端分离(三):登录、登出、页面认证_第2张图片
未登录访问API
Spring Boot之前后端分离(三):登录、登出、页面认证_第3张图片
登录
Spring Boot之前后端分离(三):登录、登出、页面认证_第4张图片
登录后访问API
Spring Boot之前后端分离(三):登录、登出、页面认证_第5张图片
登出
Spring Boot之前后端分离(三):登录、登出、页面认证_第6张图片
登出后访问API

我们会发现登出后,Cookie中的JSESSIONID已经变了,这是由于后端把原有的JSESSIONID删除了,并自动分配了另外的JSESSIONID,且是个没有权限的JSESSIONID。不仅如此,如果我们用原有的JSESSIONID再次访问API,也是没有权限的!

因此,我们完成了登出的配置,登出功能已实现!!!

2. JSESSIONID失效设置;

通常,处于安全考虑,我们希望如果一个用户在一定时间内对网站没有任何操作,那么关闭与该用户的会话,即将该用户的JSESSIONID设置为失效,那么这个问题在Spring Security中该如何做到呢?

其实这块Spring Security已经帮我们做好了,默认情况下,用户60秒无操作,则该用户的JSESSIONID将失效,如果我们想更改该时间,也很简单,只需要在项目的resources下的application.properties文件中加入如下配置:

server.servlet.session.timeout=600

这代表,用户600秒无操作,则该用户的JSESSIONID将失效(注意单位为秒)!
JSESSIONID失效后,用户再次登录后才能继续访问我们的应用!

后端再一次准备好了,接下来我打算做一下登录后的页面跳转和前端页面的认证。

3. 登录后页面跳转、页面访问认证、登出实现;

1). 前端基于axios编写logout API调用方法和认证API的调用方法;
import request from '@/utils/request'

export function getAuth() {
    return request({
        url: '/api/auth',
        method: 'get',
        params: {}
    })
}

export function logout() {
    return request({
        url: '/logout',
        method: 'get',
        params: {}
    })
}
2). 修改前端config/index.js文件中的proxyTable,如下:
...
const backendBaseUrl = 'http://127.0.0.1:8080/';
...
proxyTable: {
  '/api': {
    target: backendBaseUrl,
    changeOrigin: true,
    pathRewrite: {
      '^/api': '/api'
    }
  },
  '/login': {
    target: backendBaseUrl,
    changeOrigin: true,
    pathRewrite: {
      '^/login': '/login'
    }
  },
  '/logout': {
    target: backendBaseUrl,
    changeOrigin: true,
    pathRewrite: {
      '^/logout': '/logout'
    }
  },
}
3). 前端src/views底下创建home文件夹,home文件夹内创建index.vue文件,代码如下:

(我们暂时不考虑将某些功能组件化,此处只做演示)






稍微解读一下:

  • 关注点一:我们created()方法中调用后端的/api/auth方法,验证用户是否已登录,如果已登录,则展示用户的角色信息和当前角色信息。如果未登录,则src/utils/request.js中会将页面重定向到登录页面,src/utils/request.js的改动见下文;
  • 关注点二:我们在页面上设置了一个Sign Out入口,点击Sign Out则调用后端/logout API,并且在调用成功后重定向到登录页面;
4). 修改src/utils/request.js文件,如下:
import axios from 'axios'
import { Message, MessageBox } from 'element-ui'

// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 15000 // 请求超时时间
})

// request拦截器
/*
service.interceptors.request.use(config => {
  if (store.getters.token) {
    config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})
*/

// respone拦截器
service.interceptors.response.use(
  response => {
    /**
     * code为非200是抛错 可结合自己业务进行修改
     */
    const res = response.data;
    if (res.code !== 200) {
      Message({
        message: res.message,
        type: 'error',
        duration: 5 * 1000
      })
    }
    return res;
  },
  error => {
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    });
    window.location.href = "/#/login.html";
    return Promise.reject(error)
  }
)

export default service

唯一变化是在error内增加了window.location.href = "/#/login.html"; 这行代码,这是使得访问后端API的时候,如果遇到400,401,500等错误,直接跳转到登录页面(可另外设置页面,此处只做演示),是全局的设置,避免每个页面都写一遍类似的代码(非必需,可根据实际情况设置)。

5). 修改src/views/login/index.vue中methods的login方法,如下:
login(formName) {
  const from = this.$route.query.from || "home.html";
  this.$refs[formName].validate((valid) => {
    if (!valid) {
      return false;
    }
    login(this.ruleForm.username, this.ruleForm.password).then(
      (response) => {
        this.showSuccessNotify(response);
        this.ruleForm.username = "";
        this.ruleForm.password = "";
        if (response.code == 200 && response.status == "success") {
          this.$router.push({ path: `/${from}` });
        }
      }
    );
  });
}

这是为了让从其他页面跳转到登录页面,登录成功后可直接跳转回原有页面,是个小技巧,非必需。如果不是从其他页面跳转到登录页面的,则登录成功后默认跳转至127.0.0.1/#/home.html页面。

6). 设置访问前端路由外的报错页面,即404页面;
  • src/assets内添加404页面用到的图片资源:


    Spring Boot之前后端分离(三):登录、登出、页面认证_第7张图片
    404图片资源
  • 新建src/views/404.vue文件并编写404页面;





项目启动后,访问未在前端设置路由的路径,如http://127.0.0.1:9528/#/test.html或http://127.0.0.1:9528/#/test,则跳转到http://127.0.0.1:9528/#/404,页面长这样:

Spring Boot之前后端分离(三):登录、登出、页面认证_第8张图片
404页面

怎么样,404页面还是挺好看的吧!

7). 将src/views/home/index.vue和src/views/404.vue加入前端路由配置src/router/index.js中:

import Vue from 'vue'
import Router from 'vue-router'
import login from '@/views/login/index'
import home from '@/views/home/index'
import notFoundPage from '@/views/404'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'index',
      component: login
    },
    {
      path: '/login.html',
      name: 'login',
      component: login
    },
    {
      path: '/home.html',
      name: 'home',
      component: home
    },
    {
      path: '/404',
      component: notFoundPage,
      hidden: true
    },
    {
      path: '*',
      redirect: '/404',
      hidden: true
    }
  ]
})

4. 演示登录后页面跳转、页面访问认证、登出;

1). 启动前端:
npm start
Spring Boot之前后端分离(三):登录、登出、页面认证_第9张图片
启动前端
2). 启动后端:
Spring Boot之前后端分离(三):登录、登出、页面认证_第10张图片
启动后端
Spring Boot之前后端分离(三):登录、登出、页面认证_第11张图片
启动后端
3). 登录前访问home页面:http://127.0.0.1:9528/#/home.html;
Spring Boot之前后端分离(三):登录、登出、页面认证_第12张图片
登录前访问home页面

我们会发现页面自动重定向到登录页面,并且显示了一个401方面的错误信息,这是我们事先在src/utils/request.js内写好的哟,该行为比较符合实际使用需求!

4). 前端登录及登录跳转:
Spring Boot之前后端分离(三):登录、登出、页面认证_第13张图片
前端登录-错误信息
Spring Boot之前后端分离(三):登录、登出、页面认证_第14张图片
前端登录-正确信息
Spring Boot之前后端分离(三):登录、登出、页面认证_第15张图片
登录跳转
我们会发现:
  • 只有正确的用户登录信息才能登录,错误的用户登录信息是无法登录的,这也是符合符合实际情况的!
  • 登录后成功跳转至home页面,home页面进行了访问认证,并且认证通过,页面正常展示,符合实际情况!
5). 登出操作、登出后访问home页面:http://127.0.0.1:9528/#/home.html;
  • 点击页面右上角的Sign Out按钮进行登出;
Spring Boot之前后端分离(三):登录、登出、页面认证_第16张图片
登出
  • 登出时调用后端logout API;
Spring Boot之前后端分离(三):登录、登出、页面认证_第17张图片
登出时调用后端logout API
  • 登出后访问home页面;
Spring Boot之前后端分离(三):登录、登出、页面认证_第18张图片
登出后访问home页面

我们会发现,登出后home页面已无权限访问!

从JSESSIONID的角度来看:

  • 登录后站点Cookies中JSESSIONID的情况:
Spring Boot之前后端分离(三):登录、登出、页面认证_第19张图片
登录后JSESSIONID
  • 登出后站点Cookies中JSESSIONID的情况:
Spring Boot之前后端分离(三):登录、登出、页面认证_第20张图片
登出后JSESSIONID

可见,登出后,站点Cookies中JSESSIONID的确被删除掉了,这是后端WebSecurityConfig.java类中写好的,符合我们的预期!

至此,我们实现了登录成功后的跳转,登出功能、页面认证,并完整地做了演示。由于同时涉及前后端,整个过程稍微有点长而复杂,完整看完的话需要点耐心,我鼓励大家动手跟着实现一遍!

而我也将不断学习,不断更新Spring Boot相关知识,以及Spring Boot前后端分离相关内容。道阻且长,但不忘初心!

如果本文对您有帮助,麻烦点赞、关注!

谢谢!

你可能感兴趣的:(Spring Boot之前后端分离(三):登录、登出、页面认证)