提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:以下是本篇文章正文内容,下面案例可供参考
一、Sa-Token介绍
Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题
通俗的来讲,就是解决了我们哪些接口是需要先登录后调用的,哪些接口是添加了白名单,不需要登录就可以调用的,Sa-Token提供了简便的API及配置,能让开发更快、更高效。
二、Sa-Token使用场景
1.鉴权(登录拦截)
public class SaTokenAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws IOException {
if (StpUtil.isLogin()) {
return true;
}
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.getWriter().append(JSONUtil.toJsonStr(ResultVO.fail(ResultVOEnum.NO_AUTH)));
return false;
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册Sa-Token的认证截器
registry.addInterceptor(new SaTokenAuthInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/auth/**", "/actuator/**", "/callback/**", "/health");
}
}
上边的代码,添加了一个登录拦截器,在所有接口执行之前,先执行拦截器内的代码逻辑。
上边这个例子里,添加的逻辑是判断是否已登录,如果已登录返回true,未登录返回false,并将结果回写。 然后将拦截器注册,同时可以添加拦截路径以及白名单。
public boolean isLogin() {
return this.getLoginIdDefaultNull() != null;
}
public Object getLoginIdDefaultNull() {
if (this.isSwitch()) {
return this.getSwitchLoginId();
} else {
String tokenValue = this.getTokenValue();
if (tokenValue == null) {
return null;
} else {
Object loginId = this.getLoginIdNotHandle(tokenValue);
if (!this.isValidLoginId(loginId)) {
return null;
} else {
return this.getTokenActivityTimeoutByToken(tokenValue) == -2L ? null : loginId;
}
}
}
}
public String getTokenValue() {
String tokenValue = this.getTokenValueNotCut();
String tokenPrefix = this.getConfig().getTokenPrefix();
if (!SaFoxUtil.isEmpty(tokenPrefix)) {
if (!SaFoxUtil.isEmpty(tokenValue) && tokenValue.startsWith(tokenPrefix + " ")) {
tokenValue = tokenValue.substring(tokenPrefix.length() + " ".length());
} else {
tokenValue = null;
}
}
return tokenValue;
}
public String getTokenValueNotCut() {
SaStorage storage = SaHolder.getStorage();
SaRequest request = SaHolder.getRequest();
SaTokenConfig config = this.getConfig();
String keyTokenName = this.getTokenName();
String tokenValue = null;
if (storage.get(this.splicingKeyJustCreatedSave()) != null) {
tokenValue = String.valueOf(storage.get(this.splicingKeyJustCreatedSave()));
}
if (tokenValue == null && config.getIsReadBody()) {
tokenValue = request.getParam(keyTokenName);
}
if (tokenValue == null && config.getIsReadHead()) {
tokenValue = request.getHeader(keyTokenName);
}
if (tokenValue == null && config.getIsReadCookie()) {
tokenValue = request.getCookieValue(keyTokenName);
}
return tokenValue;
}
这段代码就是Sa-Token中的判断是否登录的源码,可以看出判断登录是从请求头中获取的登录信息,下边是一个实际案例截图
2.登录
说完登录拦截之后,再补充一下登录
public static void login(Object id) {
stpLogic.login(id);
}
public void login(Object id) {
this.login(id, new SaLoginModel());
}
public void login(Object id, SaLoginModel loginModel) {
String token = this.createLoginSession(id, loginModel);
this.setTokenValue(token, loginModel.getCookieTimeout());
}
public void setTokenValue(String tokenValue, int cookieTimeout) {
if (!SaFoxUtil.isEmpty(tokenValue)) {
this.setTokenValueToStorage(tokenValue);
if (this.getConfig().getIsReadCookie()) {
this.setTokenValueToCookie(tokenValue, cookieTimeout);
}
}
}
public void setTokenValueToStorage(String tokenValue) {
SaStorage storage = SaHolder.getStorage();
String tokenPrefix = this.getConfig().getTokenPrefix();
if (!SaFoxUtil.isEmpty(tokenPrefix)) {
storage.set(this.splicingKeyJustCreatedSave(), tokenPrefix + " " + tokenValue);
} else {
storage.set(this.splicingKeyJustCreatedSave(), tokenValue);
}
storage.set("JUST_CREATED_NOT_PREFIX_", tokenValue);
}
public void set(String key, Object value) {
this.request.setAttribute(key, value);
}
public String createLoginSession(Object id, SaLoginModel loginModel) {
SaTokenException.throwByNull(id, "账号id不能为空");
if (this.isDisable(id)) {
throw new DisableLoginException(this.loginType, id, this.getDisableTime(id));
} else {
SaTokenConfig config = this.getConfig();
loginModel.build(config);
String tokenValue = null;
if (config.getIsConcurrent()) {
if (this.getConfigOfIsShare() && (loginModel.getExtraData() == null || loginModel.getExtraData().size() == 0)) {
tokenValue = this.getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
}
} else {
this.replaced(id, loginModel.getDevice());
}
if (tokenValue == null) {
if (SaFoxUtil.isEmpty(loginModel.getToken())) {
tokenValue = this.createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout(), loginModel.getExtraData());
} else {
tokenValue = loginModel.getToken();
}
}
SaSession session = this.getSessionByLoginId(id, true);
session.updateMinTimeout(loginModel.getTimeout());
session.addTokenSign(tokenValue, loginModel.getDeviceOrDefault());
this.saveTokenToIdMapping(tokenValue, id, loginModel.getTimeout());
this.setLastActivityToNow(tokenValue);
SaManager.getSaTokenListener().doLogin(this.loginType, id, tokenValue, loginModel);
if (config.getMaxLoginCount() != -1) {
this.logoutByMaxLoginCount(id, session, (String)null, config.getMaxLoginCount());
}
return tokenValue;
}
}
public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
SaSession session = this.getSaTokenDao().getSession(sessionId);
if (session == null && isCreate) {
session = (SaSession)SaStrategy.me.createSession.apply(sessionId);
this.getSaTokenDao().setSession(session, this.getConfig().getTimeout());
}
return session;
}
default SaSession getSession(String sessionId) {
return (SaSession)this.getObject(sessionId);
}
// 从redis中读取
public Object getObject(String key) {
return this.objectRedisTemplate.opsForValue().get(key);
}
// 从内存中读取
public Object getObject(String key) {
this.clearKeyByTimeout(key);
return this.dataMap.get(key);
}
public class SaTokenConfig implements Serializable {
private static final long serialVersionUID = -6541180061782004705L;
// 重要
private String tokenName = "satoken";
// 重要
private long timeout = 2592000L;
private long activityTimeout = -1L;
private Boolean isConcurrent = true;
private Boolean isShare = true;
private int maxLoginCount = 12;
private Boolean isReadBody = true;
private Boolean isReadHead = true;
private Boolean isReadCookie = true;
private String tokenStyle = "uuid";
private int dataRefreshPeriod = 30;
private Boolean tokenSessionCheckLogin = true;
private Boolean autoRenew = true;
private String tokenPrefix;
private Boolean isPrint = true;
private Boolean isLog = false;
private String jwtSecretKey;
private long idTokenTimeout = 86400L;
private String basic = "";
private String currDomain;
private Boolean checkIdToken = false;
public SaCookieConfig cookie = new SaCookieConfig();
public SaTokenConfig() {
}
......
}
以上为登录逻辑重要的源码,登录的调用特别简单,StpUtil.login(customer.getGuid()); customer.getGuid()为用户的唯一标识,一个用户对应一个
由此可以看出,Sa-Token已经帮研发人员将逻辑都封装好了,接入还是比较简单。
3.单点登录
先登出
StpUtil.logout(Object loginId)
然后更新loginId,新生成一个loginId
StpUtil.login(loginId)
此时新的登录生效,原登录失效,原登录状态下继续操作,会被登录拦截,提示未登录。