SpringCloud整合GateWay+Sa-Token做登录验证(干货)
一、什么是Sa-Token?
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题,在这里我就不详细介绍了,想要了解的同学可以去查看官方文档 https://sa-token.cc/doc.html
二、关于GateWay
GateWay网关是我们服务的守门神,是统一入口处
核心功能特性:
1.请求路由:通过特定的规则将请求转发到对应的微服务
2.权限控制:进入不同微服务之前都要进行一个验证
3.限流:当请求流量过高时就要限流
三、具体步骤
项目结构
1、配置网关服务
在gateway模块引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>data_factory_cloud</artifactId>
<groupId>com.wjk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>data_factory_cloud_gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-reactor-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.34.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- httpClient依赖,缺少此依赖api网关转发请求时可能发生503错误 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!--redis坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version> <!-- 请使用最新的版本号 -->
</dependency>
</dependencies>
</project>
在application.yml文件里面进行配置
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: source_database
uri: lb://source_database #负载均衡
predicates: #路由断言,判断请求是否符合规则
- Path=/sourceDatabase/** #路径断言,判断路径是否是以/user开头,如果是符合
# filters: #过滤器
# - AddRequestHeader=Truth,Itcast is freaking aowsome!
- id: code
uri: lb://code
predicates:
- Path=/code/**
- id: user
uri: lb://user
predicates:
- Path=/user/**
- id: data_asset
uri: lb://data_asset
predicates:
- Path=/dataAsset/**
- id: data_standard
uri: lb://data_standard
predicates:
- Path=/order/**
- id: sourceApi
uri: lb://data_standard
predicates:
- Path=/sourceApi/**
redis:
host: 192.168.246.133
port: 6379
timeout: 10s
# password: 123456
# database: 0
lettuce:
pool:
max-active: 10
max-wait: -1
max-idle: 16
min-idle: 8
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: sa-token-authorization
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token风格
token-style: random-32
# 是否尝试从 header 里读取 Token
is-read-head: true
# 是否开启自动续签
auto-renew: true
# 临时有效期,单位s,例如将其配置为 1800 (30分钟),代表用户如果30分钟无操作,则此Token会立即过期
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时同端互斥)
is-concurrent: true
# 配置 Sa-Token 单独使用的 Redis 连接
alone-redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 192.168.246.133
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间
timeout: 10s
新建类SaTokenConfigure,实现网关拦截
package com.wjk.config;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.server.ServerWebExchange;
/**
* @author YangBoss
* @title: SaTokenConfigure
* @projectName meta
* @description: TODO
* @date 2022/8/18 10:12
*/
@Configuration
public class SaTokenConfigure {
// 注册 Sa-Token全局过滤器
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**")
// 开放地址
.addExclude("/user/login")
.addExclude("/user/sendEmail")
.addExclude("/user/regist")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
SaRouter.match("/**", "/user/login", r -> StpUtil.checkLogin());
SaRouter.match("/**", "/user/regist", r -> StpUtil.checkLogin());
SaRouter.match("/**", "/user/sendEmail", r -> StpUtil.checkLogin());
// // 角色认证 -- 拦截以 admin 开头的路由,必须具备 admin 角色或者 super-admin 角色才可以通过认证
// SaRouter.match("/admin/**", r -> StpUtil.checkRoleOr("admin", "super-admin"));
// // 权限认证 -- 不同模块, 校验不同权限
// SaRouter.match("/meta-system/**", r -> StpUtil.checkPermission("system-no"));
// SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
// SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
// SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
})
// 异常处理方法:每次setAuth函数出现异常时进入
.setError(e -> {
// 设置错误返回格式为JSON
ServerWebExchange exchange = SaReactorSyncHolder.getContext();
exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
// return new ResultJsonUtil().fail(e.getMessage());
return SaResult.error(e.getMessage());
})
.setBeforeAuth(obj -> {
// ---------- 设置跨域响应头 ----------
SaHolder.getResponse()
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*");
// 如果是预检请求,则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
.back();
});
}
}
2、配置用户服务
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>data_factory_cloud</artifactId>
<groupId>com.wjk</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>data_factory_cloud_user</artifactId>
<dependencies>
<dependency>
<groupId>com.wjk</groupId>
<artifactId>data_factory_cloud_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- sa-token权限认证框架 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.37.0</version>
</dependency>
<!-- Spring Boot Starter Mail -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>
</project>
在yml文件里面配置
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: sa-token-authorization
# token有效期,单位s 默认30天, -1代表永不过期
timeout: 3600
# token风格
token-style: random-32
# 是否尝试从 header 里读取 Token
is-read-head: true
# 是否开启自动续签
auto-renew: true
# 临时有效期,单位s,例如将其配置为 1800 (30分钟),代表用户如果30分钟无操作,则此Token会立即过期
activity-timeout: 1800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时同端互斥)
is-concurrent: true
# 配置 Sa-Token 单独使用的 Redis 连接
alone-redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 192.168.246.133
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间
timeout: 30s
编写登录接口
UserService
package com.wjk.service;
import cn.dev33.satoken.util.SaResult;
import com.baomidou.mybatisplus.extension.service.IService;
import com.wjk.entity.User;
import com.wjk.result.R;
import com.wjk.VO.UserVo;
public interface UserService extends IService<User> {
//发送验证码邮件给指定邮箱
R sendMimeMail(String email);
//随机生成6位数字验证码
String randomCode();
//用户注册,验证验证码并保存用户信息
R registered(UserVo userVo);
//登录
SaResult login(String email, String password) throws Exception;
//登出
SaResult logout();
}
UserServiceImpl
@Override
public SaResult login(String email, String password) throws Exception {
//根据用户名从数据库中查询
User user = this.getOne(Wrappers.lambdaQuery(User.class).eq(User::getEmail,email));
System.out.println(user);
if(user == null){
return SaResult.error("用户名不存在");
}
//对登录密码进行加密
boolean result = Md5Util.passwordVerify(password, user.getPassword(), Md5Util.md5Key);
if(result){
//根据用户id登录,第1步,先登录上
StpUtil.login(user.getId());
// 第2步,获取 Token 相关参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 第3步,返回给前端
return SaResult.data(tokenInfo);
}
return SaResult.error("密码错误");
}
User
package com.wjk.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@TableName("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
@TableField("username")
private String username;
@TableField("password")
private String password;
@TableField("email")
private String email;
@TableField("created_at")
private Date createdAt;
}
UserVo
package com.wjk.VO;
import lombok.Data;
@Data
public class UserVo {
private String username;
private String password;
private String email;
//验证码
private String code;
}
UserVoToUser
package com.wjk.VO;
import com.wjk.entity.User;
public class UserVoToUser {
/**
* 将表单中的对象转化为数据库中存储的用户对象,剔除表单中的验证码字段。
*
* @param userVo 表单中的对象
* @return 数据库中存储的用户对象
*/
public static User toUser(UserVo userVo) {
// 创建一个数据库中存储的对象
User user = new User();
// 赋值
user.setUsername(userVo.getUsername());
user.setPassword(userVo.getPassword());
user.setEmail(userVo.getEmail());
// 返回转化后的对象
return user;
}
}
MD5加密
package com.wjk.utils;
import org.apache.commons.codec.digest.DigestUtils;
public class Md5Util {
// 实现一个md5加解密
public final static String md5Key = "lalalalall";
/**
*
* @param strPwd 明文密码
* @param
* @return 密文
* @throws Exception
*/
//用于注册时对密码进行加密
public static String md5(String strPwd,String Key) throws Exception{
// 获取加密后的字符串
String encodeStr = DigestUtils.md5Hex(strPwd + Key);//调用加密的算法
return encodeStr;
}
/**
* 用户登录,密码验证
* @param pwdStr 明文字符串
* @param oldPwd 密文字符串
* @return
*/
public static boolean passwordVerify(String pwdStr,String oldPwd,String key) throws Exception {
//在该方法中,不需要在外面做密码加密,登录时获取到当前用户输入的密码,在方法里进行加密
String md5Pwd= md5(pwdStr,key);
if (md5Pwd.equalsIgnoreCase(oldPwd)){
return true;
}
return false;
}
}
controller
package com.wjk.controller;
import cn.dev33.satoken.util.SaResult;
import com.wjk.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class LoginController {
@Resource
private UserService userService;
@GetMapping("/login")
public SaResult login(String email, String password) throws Exception {
return userService.login(email,password);
}
@GetMapping("/logout")
public SaResult logout(){
return userService.logout();
}
}
测试
发送验证码
根据验证码进行注册
登录
我这里的端口号10010对应的是gateway模块里的yml文件里面配置的端口号,这就是统一入口处,由它进行请求转发,肯定不止向登录模块请求转发,还会向别的端口进行,那么配置也跟上面一样,在gateway里面的yml文件里面要配置
gateway:
routes:
- id: source_database
uri: lb://source_database
predicates: #路由断言,判断请求是否符合规则
- Path=/sourceDatabase/** #路径断言,判断路径是否是以/user开头,如果是符合
# filters: #过滤器
# - AddRequestHeader=Truth,Itcast is freaking aowsome!
- id: code
uri: lb://code
predicates:
- Path=/code/**
- id: user
uri: lb://user
predicates:
- Path=/user/**
- id: data_asset
uri: lb://data_asset
predicates:
- Path=/dataAsset/**
- id: data_standard
uri: lb://data_standard
predicates:
- Path=/order/**
- id: sourceApi
uri: lb://data_standard
predicates:
- Path=/sourceApi/**
该项目具体代码在gitee
https://gitee.com/wjk0321/data_factory_cloud
uri: lb://code
predicates:
- Path=/code/**
- id: user
uri: lb://user
predicates:
- Path=/user/**
- id: data_asset
uri: lb://data_asset
predicates:
- Path=/dataAsset/**
- id: data_standard
uri: lb://data_standard
predicates:
- Path=/order/**
- id: sourceApi
uri: lb://data_standard
predicates:
- Path=/sourceApi/**
该项目具体代码在gitee
https://gitee.com/wjk0321/data_factory_cloud