本系列文章着重讲的是前后端分离的后端部分接口的开发。对于大部分接口来说,我们不希望里面的数据被未登录的访客调用;希望建立服务器与用户间的”对话“。众所周知,http协议是无状态的,那么如何实现无状态连接的鉴权呢?
使用 Token 识别用户。
客户端使用用户名跟密码请求登录
服务端收到请求,去验证用户名与密码
验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
1.客户端使用用户名跟密码请求登录
2.服务端收到请求,去验证用户名与密码
账号密码登陆控制层
@PostMapping("/login")
@ApiOperation(value = "登录")
public ApiRes<Login> login(@RequestBody @Valid AccountLogin loginDto) {
return accountService.login(loginDto);
}
3.验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
服务层:先判断用户账号密码,再生成 Token 并保存 Redis,最后将结果返回
@Override
public ApiRes<Login> login(AccountLogin loginDto) {
Employee emp = employeeMapper.selectOne(
new QueryWrapper<Employee>().lambda()
.eq(Employee::getNo, loginDto.getAccount())
);
if (emp == null) {
return ApiRes.fail("帐号不存在");
}
if (!emp.getPassword().equals(EncryptUtil.EncryptString(loginDto.getPassword()))) {
return ApiRes.fail("帐号密码不正确");
}
if (emp.getRoleId() == null || emp.getEmployeeType() != EmployeeTypeEnum.支持员工.getValue()) {
return ApiRes.fail("非支持员工,禁止登陆");
}
String token = UUID.randomUUID().toString().replaceAll("-", "");
// Redis : DB.Sys -> Id:No:RoleId
redisUtil.set(token, RedisPrefixKeyEnum.Sys.toString(), emp.getId() + ":" + emp.getNo() + ":" + emp.getRoleId(), expireTime);
Role role = roleMapper.selectById(emp.getRoleId());
Login loginData = new Login().setToken(token)
.setId(emp.getId())
.setAccount(emp.getNo())
.setRealName(emp.getRealName())
.setPhone(emp.getPhone())
.setHeadImg(emp.getHeadPortrait())
.setRoleId(emp.getRoleId())
.setRoleName(role.getName())
.setRoleDescription(role.getDescription())
.setFailureTime(DateUtil.dateAddMinutes(null, expireTime));
return ApiRes.suc("登录成功", loginData);
}
4.客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
例(Vue.js):
保存在LocalStorage中:
5.客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
前端处理(Vue.js + Axios):
6.服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
在spring boot 中添加 Filter 拦截器:
@Slf4j
public class TokenAuthFilter implements Filter {
@Override
public void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) var1;
HttpServletResponse response = (HttpServletResponse) var2;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,token");
response.setHeader("Access-Control-Expose-Headers", "*");
String methods = request.getMethod();
if ("OPTIONS".equals(methods)) {
response.setStatus(HttpServletResponse.SC_OK);
}
String token = request.getHeader("token");
boolean isAuthSuccess = false;
ServletContext context = request.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context);
assert ctx != null;
RedisUtil redisUtil = ctx.getBean(RedisUtil.class);
if (Strings.isNullOrEmpty(token) || !redisUtil.exists(token, RedisPrefixKeyEnum.Sys.toString())) {
log.warn("UnAuthorized url: " + request.getRequestURI());
response.setStatus(HttpServletResponse.SC_OK);
OutputStream outputStream = response.getOutputStream();
outputStream.write(JSONObject.toJSONBytes(ApiRes.unAuthorized()));
outputStream.flush();
outputStream.close();
} else {
String idAndAccount = redisUtil.get(token, RedisPrefixKeyEnum.Sys.toString());
String[] arr = idAndAccount.split(":");
if (arr.length == 3) {
UserData userData = new UserData();
userData.setId(Integer.valueOf(arr[0]))
.setAccount(arr[1])
.setRoleId(Integer.valueOf(arr[2]));
request.setAttribute("userData", userData);
isAuthSuccess = true;
}
}
if (isAuthSuccess) {
var3.doFilter(var1, var2);
}
}
}
需要注意的是:前后端跨域的话,需要设置 response.setHeader("Access-Control-Allow-Origin", "*");
并做好 OPTIONS 类型的请求处理。
通过 token 验证之后,才会继续执行接下来的方法。
获取用户的信息也很简单,写一个接口 IBaseService.java
public interface IBaseService {
default UserData getUserData() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
UserData userData = (UserData) request.getAttribute("userData");
return userData;
}
}
服务层实现类中 implements IBaseService 就可以了:
String account = getUserData().getAccount();
Integer id = getUserData().getId();