? ? ? ?前面已经完成了Vue登录第2.2版,使用了数据库MySQL进行登录验证。下面将在第2.2版的基础上,通过token令牌的传输实现登录认证。这里称其为Vue登录第2.3版。
? ? ? ?Vue登录第2.3版登录认证实现过程大致如下:
? ? ?(1)使用Vue完成登录界面,用户输入用户名和密码,向后端程序发送登录请求,并将输入的用户名和密码一起发送到后端程序。
? ? ?(2)后端程序接受发送的用户名和密码,进行登录验证。
? ? ?(3)登录验证成功后,服务端会签发一个 Token,把这个Token再发送给客户端。
? ? ?(4)客户端收到Token以后把它存储起来,可以存放Cookie或者LocalStorage中。
? ? ?(5)客户端每次向服务端发送请求时,都需要带着服务端签发的 Token。
? ? ?(6)服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。
? ? ? ?Token登录实现的原理如图1所示。
图1
1.关于Token的简要说明
? ? ? ?Token翻译成中文常称为令牌。令牌是道教斋醮科仪中常用法器。在古代军队中也称为虎符,用于军队的将领发号施令、调兵遣将。 ? ? ? ?在很早的网络中,有一种网络称为令牌环网,学过计算机网络的都应该知道。这种网络中数据帧的传输就是通过令牌来控制实现的。 ? ? ? ?这里所说的令牌也和上面令牌其作用也是一样,体现了身份权限的认证和操作。正如有诗云:虎符调兵征诸侯,令牌号令役雷神。 ? ? ? ?在不是前后端分离的Java Web开发中,Token也用于登录验证过程中,比如:Shiro和Token一起进行登录认证等,只是因为有cookie、session等机制,Token这种机制似乎还不是很凸显,但在前后端分离开发模式中,由于安全机制的需求,令牌也真正起到了:虎符调兵征诸侯,令牌号令役雷神。
? ? ? ?关于token的生成最简单的形式是使用uuid生成字符串,作为令牌。其使用可以有很多形式,和shiro、SpringSecurity、JWT等一起结合使用。这里不展开讲解。
? ? ? ?今天,主要简要介绍sa-token机制在登录验证的使用。它的官网(https://sa-token.dev33.cn/)上的广告语:一个轻量级 java 权限认证框架,让鉴权变得简单、优雅! ? ? ? ?GitHub和码云https://gitee.com/dromara/sa-token有demo。另外,官网(https://sa-token.dev33.cn/doc/index.html#/start/example)给出了sa-token与SpringBoot集成比较详细的步骤。下面基本上是以此讲解将sa-token集成到Vue登录程序第2.3版的过程。
2.?后端程序
2.1 在pom.xml添加依赖
? ? ? ?在Vue登录程序第2.2版后端程序的pom.xml中引入sa-token的依赖包sa-token-spring-boot-starter,具体如下:
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.29.0</version>
</dependency>
2.2 在application.properties配置sa-token
? ? ? ?在application.properties配置sa-token的属性,具体如下:
#配置服务器的端口号
server.port=8082
# 连接mysql数据源的相关配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/lighthouse?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
# mybatis 相关配置
mybatis.mapper-locations=classpath:mybatis/base/userMapper.xml
# Sa-Token配置
# token名称 (同时也是cookie名称)
sa-token.token-name=satoken
# token有效期,单位s 默认30天, -1代表永不过期
sa-token.timeout=2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
sa-token.activity-timeout=-1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
sa-token.is-concurrent=true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
sa-token.is-share=false
# token风格
sa-token.token-style=uuid
# 是否输出操作日志
sa-token.is-log=false
? ? ? ?由于在application.properties冗余字段太多,在项目开发中,更多地选择后缀名为.yml的配置文件,如:application.yml,具体配置示例如下:
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: satoken
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
? ? ? ?关于这两种配置文件的区别,请百度。
2.3 对Result.java文件进行改写和重命名为ResponseResult.java
? ? ? ?Result.java文件位于包”com.solo.common.utils“中,是对后端返回前端信息的一个封装,也就是让返回给前端的是一个统一的返回响应对象,这样的好处是前后端能统一接口返回,可以做规范的响应处理。为了更加通用,这里将Result改为一个泛型类ResponseResult<T>,并重命名为:ResponseResult.java,程序代码如下:
public class ResponseResult<T> implements Serializable {
private static final long serialVersionUID = -5316018247813336695L;
/**
* 响应码或状态码
*/
private Integer code;
/**
* 信息
*/
private String message;
/**
* 返回数据
*/
private T data;
private ResponseResult(int code) {
this.code = code;
}
private ResponseResult(int code, T data) {
this.code = code;
this.data = data;
}
private ResponseResult(int code, String message) {
this.code = code;
this.message = message;
}
private ResponseResult(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public int getcode() {
return code;
}
public T getData() {
return data;
}
public String getmessage() {
return message;
}
public static <T> ResponseResult<T> errorMessage(int code, String errorMessage) {
return new ResponseResult<T>(code, errorMessage);
}
public static <T> ResponseResult<T> successMessage(int code, String message,T data) {
return new ResponseResult<T>(code, message,data);
}
public static <T> ResponseResult<T> successMessage(int code, String message) {
return new ResponseResult<T>(code, message);
}
}
2.4 在UserService.java类中增加一个新增用户的方法insert()
? ? ? ?在包com.solo.base.rbac.service下的UserService.java中添加一个新增用户的方法insert(),新增后UserService.java的代码如下:
public interface UserService {
/**
* 登录方法
* @param loginDTO
* @return 返回结果集
*/
ResponseResult login(LoginDTO loginDTO);
/**
* 新增用户的方法
* @param user
* @return 返回结果集
*/
ResponseResult insert(User user);
/**
* 根据用户名查找用户对象
* @param username
* @return 返回一个用户对象信息
*/
User selectUserByUserName(String username);
}
2.5 完善UserServiceImpl.java实现类
? ? ? ?在包com.solo.base.rbac.service.impl下的UserServiceImpl.java实现类中,修改login()方法和insert()方法,修改完成后的UserServiceImpl.java的代码如下:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public ResponseResult login(LoginDTO loginDTO) {
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
// 根据用户名进行查询,返回一个对象
User user = selectUserByUserName(username);
if (!ObjectUtils.isEmpty(user)) {
//密码错误
if (!user.getPassword().equals(password)) {
return ResponseResult.errorMessage(401, "密码错误!");
} else if (user.getPassword().equals(password)) {
//更新登录时间
user.setUpdateTime(new Date());
// 在登录时写入当前会话的账号id
// https://sa-token.dev33.cn/doc/index.html#/use/login-auth
StpUtil.login(user.getId());
// token信息Model: 用来描述一个token的常用参数
// https://sa-token.dev33.cn/doc/index.html#/fun/token-info
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 返回数据对象
Map map = new HashMap();
map.put("user", user);
map.put("tokenInfo", tokenInfo);
return ResponseResult.successMessage(200, "登录成功!", map);
}
}
return ResponseResult.errorMessage(401, "账号不存在!");
}
@Override
public ResponseResult insert(User user) {
userMapper.insert(user);
return ResponseResult.successMessage(200, "保存成功!");
}
@Override
public User selectUserByUserName(String username) {
return userMapper.selectUserByUsername(username);
}
}
2.6 修改LoginController.java类
? ? ? ?修改位于包com.solo.base.rbac.controller下的LoginController.java,将登录业务处理移至业务层来实现,修改后的LoginController.java具体代码如下:
@RestController
public class LoginController {
@Autowired
private UserService userService;
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseResult login(@RequestBody LoginDTO loginDTO) {
return userService.login(loginDTO);
}
@SaCheckLogin
@PostMapping("add")
public ResponseResult add(@RequestBody User user) {
return userService.insert(user);
}
}
2.7 修改CorsConfig.java
? ? ? ?对位于com.solo.common.config包下的CorsConfig.java类进行修改,让其实现接口WebMvcConfigurer,另外配置开启sa-token注解式鉴权功能,如在LoginController.java中方法add()上面的注解@SaCheckLogin,这样只有拥有sa-token进行登录后,才能操作。CorsConfig.java修改后的代码如下:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
/**
* 开启跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 允许跨域访问的路径
registry.addMapping("/**")
// 设置允许跨域请求的域名
.allowedOriginPatterns("*")
// 是否发送cookie
.allowCredentials (true)
// 允许头部设置
.allowedHeaders ("*")
// 允许请求方法
.allowedMethods ("*")
// 跨域允许时间
.maxAge(3600);
}
// 注册Sa-Token的注解拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
}
3.?后端程序
待续...
试玉要烧三日满,辨材须待七年期。
|