一、Satoken原理
Satoken是基于Cookie与Session的一种轻量级权限认证框架,它通过将登录成功后的用户信息存储在session中,并基于此生成一个token,再将token存储在cookie中返回给客户端,从而实现身份认证和权限校验的功能。
Satoken的核心代码如下:
public class SaTokenManager {
/**
* 创建指定user_id的token
* @param loginId 账号id
* @return token
*/
public String createToken(Object loginId) {
// 生成 sessionId
String sessionId = SessionIdGenerator.generateId();
// 创建 session
SaSession session = SaManager.getSaSession(sessionId, true);
// 标记用户已登录
session.setAttribute("loginId", loginId);
// 创建 Session-Token
String tokenValue = TokenValueGenerator.generateValue();
SaToken token = SaManager.createToken(tokenValue);
// 关联 session
SaManager.bindToken(token, session);
// 将 session id 写到 cookie
SaManager.getResponse().addCookie(SaConstant.TOKEN_NAME, tokenValue);
return tokenValue;
}
/**
* 删除指定tokenId对应的session
* @param tokenValue tokenId
*/
public void removeToken(String tokenValue) {
SaManager.logoutByTokenValue(tokenValue);
}
/**
* 根据Token-Value 获取对应userid
* @param tokenValue Token值
* @return userid,如果未登录则返回null
*/
public Object getUserIdByToken(String tokenValue) {
return SaManager.getObjectValue(tokenValue, "loginId");
}
/**
* 根据Token-Value 获取对应session
* @param tokenValue Token值
* @return session,如果未登录则返回null
*/
public SaSession getSessionByToken(String tokenValue) {
return SaManager.getSessionByToken(tokenValue);
}
}
二、Satoken跨域
Satoken可以支持跨域访问,只需要在服务器端配置一下即可。例如,我们可以使用SpringBoot来进行配置:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
三、Goldengate原理
Satoken集成了类似于Goldengate的功能,可以在登陆成功后将用户信息同步到其他应用的session中。具体实现可以继承SaTokenListener,实现onLogout、onLoginSuccess等方法即可。
以下是一个典型的Goldengate应用的代码示例:
public class GoldenGateSaTokenListener implements SaTokenListener {
/**
* 登录成功后同步用户信息到其他应用
*/
@Override
public void onLoginSuccess(String loginId, ServletRequest request, SaToken token) {
// 获取当前应用的所有Url
List<String> urlList = getApplicationUrls();
// 将用户信息同步到其他应用
for (String url : urlList) {
HttpUtil.post(url + "/goldengate/syncUser", loginId);
}
}
/**
* 用户退出时通知其他应用删除该用户的session
*/
@Override
public void onLogout(String loginId, ServletRequest request, SaToken token) {
// 获取当前应用的所有Url
List<String> urlList = getApplicationUrls();
// 将退出消息通知其他应用
for (String url : urlList) {
HttpUtil.post(url + "/goldengate/removeUser", loginId);
}
}
/**
* 获取当前应用所有Url
*/
private List<String> getApplicationUrls() {
// 省略获取Url列表的代码
}
}
四、Satoken真的好用吗
Satoken具有轻量、易用、高性能、安全等优势,其使用非常简单,十分适合做权限认证。此外,Satoken还具备 Goldengate等高级功能,可轻松支持微服务、分布式等场景。因此,Satoken值得我们去尝试使用。
五、Satoken权限认证
Satoken具有基于角色、权限、URL等粒度的权限认证功能,使用示例如下:
public class MyController {
/**
* 需要admin权限
* @return
*/
@SaCheckRole("admin")
@GetMapping("/manager")
public String manager() {
return "manager";
}
/**
* 需要user或admin权限
* @return
*/
@SaCheckPermission("user:read")
@PostMapping("/user/list")
public String userList() {
return "userList";
}
}
六、Satoken官网
Satoken的官网为:https://sa-token.dev33.cn/
七、Satoken怎么样
Satoken在功能上与传统的Shiro、Spring Security等认证框架相比并没有多大的优势,但它的性能要更加高效,并且使用起来非常方便,适合小型项目的开发。
八、Satoken微服务
Satoken完全支持微服务的使用,例如在Spring Cloud微服务架构中,可以通过配置SaTokenServerConfigurer来实现跨微服务的数据同步。代码示例如下:
@Configuration
public class SaTokenServerConfig implements SaTokenServerConfigurer {
@Autowired
private DiscoveryClient discoveryClient;
@Override
public boolean refreshTokenById(String tokenValue, String tokenKey, String tokenId) {
// 根据tokenId在所有微服务中查找该token对应的session,然后重新生成token
List<ServiceInstance> instances = discoveryClient.getInstances(SERVER_APP_NAME);
for (ServiceInstance instance : instances) {
String url = instance.getUri() + "/api/token/refresh?id=" + tokenId;
try {
restTemplate.put(url, null);
return true;
} catch (RestClientException e) {
e.printStackTrace();
}
}
return false;
}
}
九、Satoken Gateway
Satoken也支持在Spring Cloud Gateway中使用,只需要在gateway中添加一个TokenFilter即可,该filter可以实现用户身份认证、权限校验等功能。具体实现可以参考以下代码:
public class TokenFilter implements GatewayFilter, Ordered {
@Autowired
private UserService userService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 认证用户身份
String tokenValue = getAuthorizationToken(exchange.getRequest());
SaToken token = SaManager.checkToken(tokenValue);
if (!token.isLogin()) {
return ResponseUtil.unauthorized(exchange, "未登录或已过期");
}
// 校验用户权限
String uri = exchange.getRequest().getURI().getPath();
boolean hasPermission = userService.checkPermission(token.getObjectId(), uri);
if (!hasPermission) {
return ResponseUtil.forbidden(exchange);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -200;
}
}