JA Plus 开发者文档 JA Plus 开发者文档
首页
📖 白皮书 (opens new window)
  • 名词解释
  • 快速开始

    • 使用jap-simple
    • 使用jap-social
    • 使用jap-oauth2
    • 使用jap-oidc
    • 使用jap-sso
    • 使用jap-mfa
    • 使用jap-http-api
    • 使用jap-ldap
    • 错误代码
  • IDS

    • 简介
    • 快速开始
    • 自定义scope
    • 自定义登录页面
    • 自定义确认授权页面
    • 自定义缓存
    • 自定义Token加密密钥
    • 使用PKCE模式
    • 自动授权
    • 错误代码
  • starter

    • jap-spring-boot-starter
    • jap-simple-spring-boot-starter
    • jap-social-spring-boot-starter
    • jap-oauth2-spring-boot-starter
    • jap-oidc-spring-boot-starter
  • jap-ids的demo
  • 前后端分离架构中使用JAP
  • SpringBoot中使用JAP
  • 问题反馈
  • 项目问题
  • 异常问题
  • 功能问题
  • 数据看板🔥
  • 贡献指南
  • 行为准则
  • 用户权益
  • 贡献者们
  • 社区配套 (opens new window)
  • 教程
  • 投稿
  • 资讯
  • 关于
  • 友情链接
  • 捐赠列表
  • 其他开源
  • 更新记录
收藏
GitHub (opens new window)

FuJie Team

以开源的形式赋能开发者. Just auth into any app.
首页
📖 白皮书 (opens new window)
  • 名词解释
  • 快速开始

    • 使用jap-simple
    • 使用jap-social
    • 使用jap-oauth2
    • 使用jap-oidc
    • 使用jap-sso
    • 使用jap-mfa
    • 使用jap-http-api
    • 使用jap-ldap
    • 错误代码
  • IDS

    • 简介
    • 快速开始
    • 自定义scope
    • 自定义登录页面
    • 自定义确认授权页面
    • 自定义缓存
    • 自定义Token加密密钥
    • 使用PKCE模式
    • 自动授权
    • 错误代码
  • starter

    • jap-spring-boot-starter
    • jap-simple-spring-boot-starter
    • jap-social-spring-boot-starter
    • jap-oauth2-spring-boot-starter
    • jap-oidc-spring-boot-starter
  • jap-ids的demo
  • 前后端分离架构中使用JAP
  • SpringBoot中使用JAP
  • 问题反馈
  • 项目问题
  • 异常问题
  • 功能问题
  • 数据看板🔥
  • 贡献指南
  • 行为准则
  • 用户权益
  • 贡献者们
  • 社区配套 (opens new window)
  • 教程
  • 投稿
  • 资讯
  • 关于
  • 友情链接
  • 捐赠列表
  • 其他开源
  • 更新记录
收藏
GitHub (opens new window)
  • 教程

  • 投稿

    • 博客投稿栏目说明
    • 经验分享

    • 框架集成

      • Jap 与 Sa-Token 结合使用
        • 已知问题
        • 为什么会出现这种情况?
        • Jap-Simple 与 Jap-Social
          • 修改方法
        • 其他 Jap 实现如何处理?
        • Post Body 是 Json
          • 修改方法
          • Jap-Simple
          • Jap-Social
  • 资讯

  • 博客
  • 投稿
  • 框架集成
陈泉
2021-10-06

Jap 与 Sa-Token 结合使用

# 已知问题

当你了解 Sa-Token 后你会发现,Sa-Token 中 Session 与当前 HttpServer 中的 Session 不是同一个对象,Jap 默认获取的是 HttpServer 中的 Session 。

  1. 当你 Jap 中登陆, Sa-Token 未登录。
  2. 调度 Sa-Token 中登陆方式,前后端分离项目获取不到 Token。

注意 ⚠

  1. 这里的 HttpServer 代指当前程序中 Http 服务端
  2. Sa-Token 是独立的,如果需要使用他的 Session 你需要额外处理 Jap

# 为什么会出现这种情况?

  1. Sa-Token 登陆后会根据你的配置创建对应的 Session 同时往 Cookie 添加一个对应 ID 的记录
  2. 前后端分离情况下 Sa-Token 在 Login 后需要 StpUtil.getTokenInfo() 方法将 TokenName 和 TokenValue 返回给前端,前端来进行请求携带
  3. 另外是由于 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());
}
1
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());
}
1
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();
            }
        }
    }
}
1
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()))
                );
}
1
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;
}
1
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);
}
1
2
3
4
5
#jap#sa-token#投稿
Last Updated: 2021/10/07, 18:03:43
经验总结:关于为 JAP 开发不同语言的 Demo 的总结
博客资讯栏目说明

← 经验总结:关于为 JAP 开发不同语言的 Demo 的总结 博客资讯栏目说明→

最近更新
01
经验总结:关于为 JAP 开发不同语言的 Demo 的总结
11-02
02
jap-spring-boot-starter 使用帮助
10-28
03
使用jap-ldap
10-25
更多文章>
Theme by Vdoing | Copyright © 2021-2022

友情链接:UniAdmin | 江如意的博客

Copyright © 2021-2040 FUJIE. All rights reserved. 北京符节科技有限公司版权所有 | 京ICP备2020044519号-4
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式