SpringCloud+Sa-Token+Redis做登录校验
一、基本的登录校验用户名和密码是否正确(不含邮箱验证等)
1、引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mysql 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<!-- mybatis_plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!-- hutool工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--阿里巴巴数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.2</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>
</dependencies>
2、配置application.yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db2019?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
username: root
password: 210278
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
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
main:
allow-bean-definition-overriding: true
servlet:
multipart:
max-file-size: -1
max-request-size: -1
aop:
auto: true
# 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: 10s
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #表示日志将被输出到标准输出(即控制台)。
global-config:
banner: off #通常,Spring Boot 应用程序在启动时会在控制台打印一个 ASCII 风格的横幅图案。将其设置为 off 可以关闭这个图案的显示,使得控制台输出更加简洁
3、建立entity、service、controller、mapper
项目结构
entity-users
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@TableName("users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Users {
@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;
}
mapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wjk.entity.Users;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
}
service
import com.baomidou.mybatisplus.extension.service.IService;
import com.wjk.entity.Users;
public interface UserService extends IService<Users> {
}
serviceImpl
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wjk.entity.Users;
import com.wjk.mapper.UsersMapper;
import com.wjk.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UsersMapper,Users> implements UserService {
}
controller
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.wjk.entity.Users;
import com.wjk.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class LoginController {
@Resource
private UserService userService;
@GetMapping("/login")
public SaResult login(String username, String password){
//根据用户名从数据库中查询
Users users = userService.getOne(Wrappers.lambdaQuery(Users.class).eq(Users::getUsername,username));
System.out.println(users);
if(users == null){
return SaResult.error("用户名不存在");
}
if(!users.getPassword().equals(password)){
return SaResult.error("密码错误");
}
//根据用户id登录,第1步,先登录上
StpUtil.login(users.getId());
// 第2步,获取 Token 相关参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 第3步,返回给前端
return SaResult.data(tokenInfo);
}
@GetMapping("/logout")
public SaResult logout(){
StpUtil.logout();
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
Boolean isLogin = tokenInfo.isLogin;
if(!isLogin){
return SaResult.ok();
}
return SaResult.error("退出失败");
}
}
4、建立configure配置类
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/login");
}
}
5、建立拦截器
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class SaInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
if (StpUtil.isLogin()) {
// 如果已经登录,则放行
return true;
} else {
// // 如果未登录,重定向到登录页面
// response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
}
6、密码加密
做权限认证先做管理员,上帝权限,可以执行一切操作
权限认证是加在需要权限的方法接口上面的,如删除,就在含有删除方法的接口中加这个权限
想办法将这些权限存放在redis中
什么叫单点登录?
发送邮箱验证码疑问
spring: mail: host: smtp.qq.com username: xxxxx@qq.com password: xxxxxx这里改成自己的邮箱是不是意思是从我的邮箱向要注册的邮箱发送注册编码?
不完全是这样。在Spring Boot中配置邮件服务,并不仅仅是指从您的邮箱地址发送邮件,而是配置应用程序使用您提供的邮箱账户信息来发送邮件。具体来说:
spring.mail.host
: 指定邮件服务器的主机地址,这里配置的是腾讯QQ邮箱的SMTP服务器地址。spring.mail.username
: 指定用于发送邮件的邮箱地址。这里填写的邮箱地址将作为发件箱,应用程序会使用这个邮箱地址来发送邮件。spring.mail.password
: 指定发送邮件时使用的密码或授权码。对于QQ邮箱,您可能需要生成一个专门的授权码而不是直接使用您的邮箱登录密码。
当您在应用程序中实现注册功能时,应用程序会使用这些配置信息来发送邮件。例如,当一个用户注册时,应用程序可以生成一个注册验证码,并通过邮件发送给用户。用户收到邮件后,可以将验证码输入到注册表单中以完成注册过程。
这里的关键点是:
- 不是您的邮箱客户端:应用程序不是使用您的邮箱客户端来发送邮件,而是使用Spring Framework的邮件发送功能,通过SMTP协议直接与邮件服务器通信来发送邮件。
- 应用程序控制:是您的应用程序在控制邮件的发送,而不是您手动发送邮件。
- 安全性:使用授权码而不是邮箱密码可以提高安全性,因为授权码是专门生成的,并且可以设置有效期和使用限制。
因此,当您将这些配置信息改为您自己的邮箱信息时,意味着您的应用程序将使用您的邮箱账户来发送注册验证码等邮件,而不是从您的个人邮箱客户端发送。
注册校验与将验证码存放到redis中,密码加密
使用全局异常处理器来处理未登录时报的错NotLoginException
要实现微服务的登录校验必须用到网关
4.25学习网关
为什么不直接gateway进行校验呢?
直接在 Spring Cloud Gateway 进行校验是完全可行的,并且通常是微服务架构中的常见做法。事实上,网关(Gateway)是进行跨服务的全局认证和授权的理想位置,原因包括:
- 统一认证:网关可以统一处理所有入口请求的认证,确保所有服务都受到同一套安全策略的保护。
- 性能优化:在网关进行认证可以减少后端服务的负担,因为一旦请求通过网关的认证,后端服务就不需要再处理相同的认证逻辑。
- 简化后端服务:后端服务可以假设通过网关的请求已经是认证过的,这样就可以专注于业务逻辑而不是安全逻辑。
- 跨服务的全局策略:网关可以实施跨服务的全局安全策略,如 CORS 支持、速率限制、日志记录等。
- 安全性:网关可以作为所有服务的单一信任点,通过它可以实现更集中的安全控制。
然而,也存在一些情况,你可能希望在网关之后,进一步使用像 Sa-Token 这样的框架在具体的微服务中进行细粒度的权限校验:
- 细粒度权限控制:在网关层进行的是粗粒度的认证,而具体的权限控制可能需要在服务层实现。
- 服务特定规则:不同的服务可能有不同的安全要求,使用 Sa-Token 可以在每个服务中实现特定的认证和授权逻辑。
- 灵活的认证机制:Sa-Token 提供了多种认证机制,如会话认证、Token认证等,可以根据服务的需要灵活选择。
- 避免暴露服务:即使请求通过了网关的认证,某些服务可能还需要额外的校验来确保安全性,例如,防止内部服务被外部直接访问。
- 业务需求:某些复杂的业务场景可能需要在服务层面进行更细致的控制。
- 防止绕过:作为额外的安全层,即使攻击者绕过了网关,服务级别的校验仍然可以防止未授权的访问。
在实践中,通常会结合使用网关和微服务内部的校验机制,以实现多层防御策略。网关负责初步的、全局的认证和授权,而微服务内部的校验负责更精细的权限控制和业务逻辑相关的安全需求。这种多层安全策略可以提供更全面的保护。
服务特定规则*:不同的服务可能有不同的安全要求,使用 Sa-Token 可以在每个服务中实现特定的认证和授权逻辑。
3. 灵活的认证机制:Sa-Token 提供了多种认证机制,如会话认证、Token认证等,可以根据服务的需要灵活选择。
4. 避免暴露服务:即使请求通过了网关的认证,某些服务可能还需要额外的校验来确保安全性,例如,防止内部服务被外部直接访问。
5. 业务需求:某些复杂的业务场景可能需要在服务层面进行更细致的控制。
6. 防止绕过:作为额外的安全层,即使攻击者绕过了网关,服务级别的校验仍然可以防止未授权的访问。
在实践中,通常会结合使用网关和微服务内部的校验机制,以实现多层防御策略。网关负责初步的、全局的认证和授权,而微服务内部的校验负责更精细的权限控制和业务逻辑相关的安全需求。这种多层安全策略可以提供更全面的保护。
思路:先创建一个权限表,在自定义权限类中通过从数据库获取权限再放进该类,将auth模块注册在注册中心,gateway模块进行调用,登录成功之后,gateway通过检验satoken返回的token来判断是否登录成功,如果登录成功则可访问其他微服务,gateway就要建立一个全局过滤器来拦截其他路径,比如一个测试微服务模块