撸一个基于VUE的WEB管理后台(三)

通过上一片文章的分析,我们大致可以了解到前端项目是采用token机制来实现用户认证的。关于token认证的相关机制已经有很多资料进行论述,就实现机制来看,可以分为几大类:

  • 类似我们熟悉的session机制,服务端在缓存中保留token的所有状态,每次请求都去缓存中检查token状态。这个方案中服务端和前端使用的token只需一个ID(用于标识服务端缓存中token的索引)即可
  • refresh token机制,这个方案中token自身通过加密的方式存储了用户的身份信息以及过期时间等信息,每次请求服务端通过解析token获取用户身份信息,当token过期反馈给前端,前端通过refresh token接口与服务端一起刷新token数据。这便是无状态token方案。
  • 还有适用于认证服务、应用服务以及用户之间相互不信任的OAuth认证机制。

以上方案各有优缺点,也有各自的适用场景,因为我们平台数据和用户并不多,短期内也没有横向扩展需求,也没有单点登录的需要,为了简便,我更倾向于采用session机制来实现。

SpringBoot内置的HttpServletRequest就包含有简单的session管理,但他默认的session是基于cookie的,而我们前端是采用http header发送token的,为了让服务端支持header的方式接受session token,就需要我们定制一下session管理器了。

基于HTTP HEADER传输SESSION ID

为了实现从header获取session id,我们可以自己注册一个Filter,在这个Filter里用我们自定义的HttpServletRequestWrapper将HttpServletRequest进行包装,将其所有与Session相关的接口重写,这样在后面各类接口使用request.getSession()时就会调用我们自己实现的Session接口。

然而,我们知道,使用Spring框架最大的好处就是,几乎所有WEB应用开发时你想要的功能,他即使暂时没有实现,也帮你考虑到了。Spring Session 就解决了我们的这个问题。来自Spring Session官网的一段介绍:

HttpSession - allows replacing the HttpSession in an application container (i.e. Tomcat) neutral way, with support for providing session IDs in headers to work with RESTful APIs

得益于Spring的良好设计,有兴趣的可以去看Spring Session是如何解决这个问题的 https://docs.spring.io/spring-session/docs/2.2.2.RELEASE/reference/html5/#httpsession-how

本质上Spring Session应该和我前面提到的自己实现一套Wrapper的原理是一样的,只是我们不再需要重复造轮子了。

我们来到 pom.xml 为后端项目加上spring-session-core这个模块。

<dependencies>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-core</artifactId>
    </dependency>

大概了解一下SpringHttpSessionConfiguration,然后阅读一下spring session的文档,可以知道直接使用session包里赠送的HeaderHttpSessionIdResolver和MapSessionRepository,就可以达到我们的目的,我们新建一个配置类SessionConfiguration 类配置它,注意我们前端传Token用的是X-Token这个header。HeaderHttpSessionIdResolver默认为“X-Auth-Token”

package com.zy.report.config;

//省略各种 import...

@EnableSpringHttpSession
public class SessionConfiguration {
    @Bean
    public HttpSessionIdResolver httpSessionStrategy() {
        return new HeaderHttpSessionIdResolver("X-Token");
    }
    @Bean
    public SessionRepository repository() {
        return new MapSessionRepository(new ConcurrentHashMap<>());
    }
}

然后为Controller中的api添加一点内容,login这块,加上session的初始化,模拟从数据库中取出了用户名字和权限,并将之存入session对象

public ApiResult login(HttpServletRequest request, @RequestBody ReqLogin params) {
    if(!(params.getUsername().equals("admin") && params.getPassword().equals("111111")))
        return new ApiResult(20001, null, "用户名或密码不正确");

    HttpSession session = request.getSession(true);
    session.setAttribute("name", "管理员");
    session.setAttribute("role", "admin");
    System.out.println("New Token: " + session.getId());

    Map<String, Object> r = new HashMap<>();
    r.put("token", session.getId());
    return new ApiResult(20000, r);
}

info接口这块,从session中获取用户名字和权限,将其返回给前端

public ApiResult info(HttpServletRequest request){
    HttpSession session = request.getSession();
    if(session == null)
        return new ApiResult(20001, null, "无效的token");
    System.out.println("Your token: " + session.getId() + ", and your name is: " + 
    	session.getAttribute("name") + " and role is: " + session.getAttribute("role"));

    Map<String, Object> r = new HashMap<>();
    r.put("username", "111111");
    r.put("name", session.getAttribute("name"));
    List<String> roles = new ArrayList<>();
    roles.add(session.getAttribute("role").toString());
    r.put("roles", roles);
    return new ApiResult(20000, r);
}

最后把前端项目 api/user.js 中 /user/info接口多余的调用参数去掉。

export function getInfo(token) {
  return request({
    url: '/user/info',
    method: 'get',
    //params: { token }
  })
}

将浏览器里的cookie清除,再次访问 http://localhost:9527/report,使用默认用户名密码登录,结果显示登陆成功,前端进入dashboard页面,api server的终端输出为

New Token: d4595618-11a7-4023-bbd6-44670b76dadd
Your token: d4595618-11a7-4023-bbd6-44670b76dadd, and your name is: 管理员 and role is: admin

这两行分别由login和info接口输出,符合预期,再来看一下浏览器的Network记录

login请求的头部与数据

POST /user/login HTTP/1.1
Host: localhost:10200
Connection: keep-alive
#部分省略
Sec-Fetch-Mode: cors
Referer: http://localhost:9527/report
{"username":"admin","password":"111111"}

login请求的响应头与数据

HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: *
X-Token: d4595618-11a7-4023-bbd6-44670b76dadd
#部分省略
Connection: keep-alive
{"code":20000,"data":{"token":"d4595618-11a7-4023-bbd6-44670b76dadd"},"message":null}

info请求的头部与数据

Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
#省略
X-Token: d4595618-11a7-4023-bbd6-44670b76dadd

info请求的响应头与数据

HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
#省略
Connection: keep-alive
{"code":20000,"data":{"roles":["admin"],"name":"管理员","username":"111111"},"message":null}

可以看到,前端与api server的确已经采用X-Token的header来交互token数据了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值