前言
有一次在码云闲逛被它的标题吸引到了,"一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!",既然你都说简单、优雅了,那我倒要看看是不是真如你所说,然后我就是一顿淦。了解完之后我只能说句牛逼!不用自定义Realm,不用写全局过滤器,各种配置文件你都不用写!只需
StpUtil.login(Object id);// 标记当前会话登录的账号id
这就完成了登录授权。同学们是不是已经合不上了,当你用过shiro、SpringSecurity后你就会知道这个框架用起来有多爽。下面做个简单的权限认证系统,加深一下理解。
一、依赖引入
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.28.0</version>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.28.0</version>
</dependency>
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.28.0</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- hutool java工具库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.19</version>
</dependency>
二、完整application.yml
spring:
profiles:
active: dev
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/testdb?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: root
application:
name: springboot-test
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
# password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 8011
# Sa-Token配置
sa-token:
# token名称 (同时也是cookie名称)
token-name: Authorization
# token有效期 设为一天 (必定过期) 单位: 秒
timeout: 86400
# token临时有效期 (指定时间无操作就过期) 单位: 秒
activity-timeout: 1800
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# 是否尝试从请求体里读取token
is-read-body: false
# 是否尝试从header里读取token
is-read-head: true
# 是否尝试从cookie里读取token
is-read-cookie: false
# token前缀
token-prefix: "Bearer"
# token风格
token-style: uuid
# jwt秘钥
jwt-secret-key: jdagsderfsgsderwq
# 是否输出操作日志
is-log: false
注意!!这个系统用到了Mybatis-Plus,不懂的同学可以看看我之前的文章**《Springboot插件集成(二)- MyBatis增强插件Mybatis-Plus》
三、业务代码
1、服务层
public interface UserService extends IService<User> {
User selectUserByUserName(String userName);
}
2、业务层处理
@Service("UserService")
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
@Override
public User selectUserByUserName(String userName) {
User user = this
.getOne(new QueryWrapper<User>()
.lambda()
.eq(User::getUserName, userName));
return user;
}
}
3、控制层
@Slf4j
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class LoginController {
private final UserService userService;
// 测试登录 ---- http://localhost:8011/test/doLogin?username=test3&password=123
@GetMapping("/doLogin")
public SaResult doLogin(String username, String password) {
User user = userService.selectUserByUserName(username);
if (user == null) {
log.info("登录用户:{} 不存在.", username);
return SaResult.error("用户不存在");
}
if (!password.equals(user.getPassword())) {
log.info("登录用户:{} 账号密码不匹配.", username);
return SaResult.error("账号密码不匹配");
}
// 标记当前会话登录的账号id
StpUtil.login(user.getId());
// 生成token
String tokenValue = StpUtil.getTokenInfo().getTokenValue();
// 缓存user对象
StpUtil.getTokenSession().set("loginUser", user);
Map<String, String> result = new HashMap<String, String>();
result.put("token", tokenValue);
return SaResult.ok().setData(result);
}
// 测试注销 ---- http://localhost:8011/test/logout
@GetMapping("/logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
// 测试获取登录用户名 ---- http://localhost:8011/test/getSession
@GetMapping("/getSession")
public SaResult getSession() {
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();
User userInfo = (User) StpUtil.getTokenSession().get("loginUser");
String userName = userInfo.getUserName();
return SaResult.ok().setData(userName);
}
}
4、domain
@Data
@TableName("t_user")
public class User {
private Long id;
private String userName;
private String password;
private Integer age;
private String email;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
5、路由拦截配置类
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册Sa-Token的路由拦截器
registry.addInterceptor(new SaRouteInterceptor())
//拦截所有
.addPathPatterns("/**")
//排除/test/doLogin 用于开放登录
.excludePathPatterns("/test/doLogin");
}
}
6、异常处理类(处理鉴权失败产生的异常)
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NotLoginException.class)
public SaResult handleAccessDeniedException(NotLoginException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return SaResult.error().setCode(HttpStatus.HTTP_UNAUTHORIZED).setMsg(StrUtil.format("请求地址'{}',认证失败,无法访问系统资源", requestURI));
}
}
四、测试
1、测试登录方法
可以看到已经登录成功,并返回了token。
2、测试获取当前登录用户名
首先不提交token看看
不提交token时,显示认证失败,无法访问。
再加上token提交看看
可以看到已成功获取到了当前登录的用户名。
3、测试登出
显示已经登录成功,再用刚才的token访问获取当前登录用户名接口试试。
可以看到刚才的token已经失效了。
结语
sa-token还能完成很多的功能,等待同学们去探索。最后附上sa-token官方文档的链接,有兴趣的可以去一探究竟。Sa-Token官方文档