Jap 与 Sa-Token 结合使用
# 已知问题
当你了解 Sa-Token 后你会发现,Sa-Token 中 Session 与当前 HttpServer 中的 Session 不是同一个对象,Jap 默认获取的是 HttpServer 中的 Session 。
- 当你 Jap 中登陆, Sa-Token 未登录。
- 调度 Sa-Token 中登陆方式,前后端分离项目获取不到 Token。
注意 ⚠
- 这里的 HttpServer 代指当前程序中 Http 服务端
- Sa-Token 是独立的,如果需要使用他的 Session 你需要额外处理 Jap
# 为什么会出现这种情况?
- Sa-Token 登陆后会根据你的配置创建对应的 Session 同时往 Cookie 添加一个对应 ID 的记录
- 前后端分离情况下 Sa-Token 在 Login 后需要
StpUtil.getTokenInfo()
方法将 TokenName 和 TokenValue 返回给前端,前端来进行请求携带 - 另外是由于 Jap 中所有操作后修改的都是当前 HttpServer 的 Session
既然 Jap 中操作的 Session 是 HttpServer ,把它统一的修改成操作 Sa-Token 的 Session 就可以正确的获取到信息。
# Jap-Simple 与 Jap-Social
Jap 默认会从 HttpServer Session 和 Cookie 中读取 UserId 并返回 JapUser 对象。
# 修改方法
创建新的类比如 CustomSimpleStrategy
继承 com.fujieid.jap.simple.SimpleStrategy
然后将 com.fujieid.jap.simple
中的方法拷贝至 CustomSimpleStrategy
(拷贝方法是因为部分方法私有,继承后无法调用)
在 authenticate
方法中存在以下代码
JapUser sessionUser = null;
try {
sessionUser = this.checkSessionAndCookie(simpleConfig, request, response);
} catch (JapException e) {
return JapResponse.error(e.getErrorCode(), e.getErrorMessage());
}
2
3
4
5
6
this.checkSessionAndCookie
就是从 HttpServer Session 中检出用户的方法修改成 Sa-Token 中获取用户的方式修改成以下
JapUser sessionUser = null;
try {
String userId = null;
// 不要使用 getLogin 默认会抛出错误
Object stpUserId = StpUtil.getLoginIdDefaultNull();
if (stpUserId != null) {
userId = (String) stpUserId;
}
if (userId != null) {
// 自己实现的 japUserService
sessionUser = japUserService.getById(userId);
}
} catch (JapException e) {
return JapResponse.error(e.getErrorCode(), e.getErrorMessage());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 其他 Jap 实现如何处理?
当你看到这里,你就明白只要重写对应的 Strategy 中从 Session 中获取方法修改成 Sa-Token 对应的方式就行。
注意 ⚠
这里没有处理 RememberMe 功能 如果你需要,你可以从 Sa-Token 中设置。
# Post Body 是 Json
默认是不会去解析 Post 正文中的 Json ,这样在前后端分离的项目中(统一都是用 Json 作为传递数据的格式),前端请求需要额外的注意当前接口的 ContentType,对前端不太友好。
# 修改方法
SpringBoot 中 Request Body 只允许读取一次你可能需要以下方法
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import lombok.extern.slf4j.Slf4j;
@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*")
@Slf4j
public class MultiReadHttpFilter extends OncePerRequestFilter {
public MultiReadHttpFilter() {
log.info("重复读取 Body 功能已开启");
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
new CachedBodyHttpServletRequest(httpServletRequest);
filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
}
private static class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new CachedBodyServletInputStream(this.cachedBody);
}
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
private static class CachedBodyServletInputStream extends ServletInputStream {
private final InputStream cachedBodyInputStream;
public CachedBodyServletInputStream(byte[] cachedBody) {
this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
}
@Override
public boolean isFinished() {
try {
return cachedBodyInputStream.available() == 0;
} catch (IOException e) {
// e.printStackTrace();
}
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
throw new UnsupportedOperationException();
}
@Override
public int read() throws IOException {
return cachedBodyInputStream.read();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# Jap-Simple
重写方法 doResolveCredential
private UsernamePasswordCredential doResolveCredential(JapHttpRequest request, SimpleConfig simpleConfig) throws
IOException {
String username = request.getParameter(simpleConfig.getUsernameField());
String password = request.getParameter(simpleConfig.getPasswordField());
if (null == username || null == password) {
Map postBody = RequestUtils.convertStreamObject(request.getReader());
username = (String) postBody.get(simpleConfig.getUsernameField());
password = (String) postBody.get(simpleConfig.getPasswordField());
}
if (null == username || null == password) {
return null;
}
return new UsernamePasswordCredential()
.setUsername(username)
.setPassword(password)
.setRememberMe(
BooleanUtil.toBoolean(request.getParameter(simpleConfig.getRememberMeField()))
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Json 解析方法 这里使用的 Jackson 你可以置换成你项目中的处理方式
public static Map convertStreamObject(BufferedReader reader) throws IOException {
String bodyString = IOUtils.toString(reader);
ObjectMapper mapper = new ObjectMapper();
Map result = mapper.readValue(bodyString, Map.class);
return result;
}
2
3
4
5
6
# Jap-Social
重写方法 parseRequest
private AuthCallback parseRequest(JapHttpRequest request) throws IOException {
ObjectMapper mapper = new ObjectMapper();
String json = IOUtils.toString(request.getReader());
return mapper.readValue(json, AuthCallback.class);
}
2
3
4
5