jap-solon-plugin
此插件,由社区成员(浅念)贡献
<dependency>
<groupId>org.noear</groupId>
<artifactId>jap-solon-plugin</artifactId>
</dependency>
1、概述
Just Auth Plus 向内为 Solon 提供了 密码登录、社会化登录、Mfa二次验证等其它身份验证的能力。但是请注意 Just Auth Plus 方面目前只完成了 Simple、Socail 以及 Mfa 部分,如果你对 OICD、LADP、HTTP API 的适配有需求,请添加 QQ 群 22200020,大鸽子浅念子会光速适配。
2、配置示例
jap:
# Auth 控制器注册根路径
authPath: /auth
# Account 控制器注册根路径
accountPath: /account
# 生成 Mfa QRCode 时 Issuer
issuer: SakuraImpression
# JapConfig 映射
japConfig:
# 是否启用单点登录
sso: true
# SSOConfig 映射
ssoConfig:
# 指定了 Cookie 使用的域名
cookieDomain: 127.0.0.1:6040
# SimpleConfig 映射
# !!! 指定该项启动 Simple !!!
simpleConfig:
# 指定了 remberMe 加密的 盐
credentialEncryptSalt: a1f735ed0cffd6f5ea80f8ee7ba68d02
# 社会化登录第三方整数列表
# 其中的每一项均为 AuthConfig 映射
# !!! 指定该项启动 Social !!!
credentials:
gitee:
clientId: b002b405304bd0b384029e8b04017349026bcca5cfe73cc6f5ca047cc4fe9241
clientSecret: 7942f86793e5fc1b73f8e3b2f2ee1925b0c9e923b0819a32097048bbeb15b5
redirectUri: http://127.0.0.1:6040/auth/social/gitee
github:
clientId: c394492091a984o659bc
clientSecret: 60c82d553f1b0dac17f2164eabc4ac63ffc831ca
redirectUri: http://127.0.0.1:6040/auth/social/github/callback
# 下一跳地址白名单,验证成功或失败后的跳转地址
# 更是确定是否为前后端分离的重要请求参数
nexts:
- https://passport.liusuyun.com/
- /auth/social/bind
- http://127.0.0.1:8000/auth/social#data={data}&code={code}
3、应用示例
我们推荐封装一个公共 Service 用于用户查询,因为会反复使用相同代码。
/**
* @author 颖
*/
public abstract class JapService extends AbstractUtils {
@Db
UserMapper userMapper;
protected User findUser(String username) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username)
.or().eq("email", username);
return this.userMapper.selectOne(queryWrapper);
}
protected User findUser(Long id) {
return this.userMapper.selectById(id);
}
}
Mfa 服务具体实现
/**
* @author 颖
*/
public class JapMfaServiceImpl extends JapService implements JapMfaService {
/**
* 根据帐号查询 secretKey
*
* @param userName 申请 secretKey 的用户
* @return secretKey
*/
@Override
public String getSecretKey(String userName) {
User user = this.findUser(userName);
return user == null ? null : user.getSecret();
}
/**
* 将 secretKey 关联 userName 后进行保存,可以存入数据库也可以存入其他缓存媒介中
*
* @param userName 用户名
* @param secretKey 申请到的 secretKey
* @param validationCode 当前计算出的 TOTP 验证码
* @param scratchCodes scratch 码
*/
@Override
public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
User user = this.findUser(userName);
if(user == null) {
throw new IllegalArgumentException();
}
user.setSecret(secretKey);
this.userMapper.updateById(user);
}
}
默认用户服务实现
我们重写了 SocialStrategy 实现了登录用户也可以绑定社交账号的能力。
感谢 @738628035 的提醒,对 WeChat 系列产品进行统一处理,使用 unionId 代替默认的 openId,来减少 微信公众号 和 微信 分家的的隐患(雾
/**
* @author 颖
*/
public class JapUserServiceImpl extends JapService implements JapUserService {
@Db
UserBindingMapper userBindingMapper;
public static String WE_CHAT_SOURCE_PREFIX = "WECHAT";
@Override
public JapUser getById(String userId) {
return this.convert(
this.findUser(Long.parseLong(userId))
);
}
@Override
public JapUser getByName(String username) {
return this.convert(
this.findUser(username)
);
}
@Override
public boolean validPassword(String password, JapUser user) {
boolean success = BCrypt.checkpw(password, user.getPassword());
if (success) {
// 删除敏感数据
user.setPassword(null);
}
return success;
}
/**
* 根据第三方平台标识(platform)和第三方平台的用户 uid 查询数据库
*
* @param platform 第三方平台标识
* @param uid 第三方平台的用户 uid
* @return JapUser
*/
@Override
public JapUser getByPlatformAndUid(String platform, String uid) {
QueryWrapper<UserBinding> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("platform", AuthDefaultSource.valueOf(platform.toUpperCase(Locale.ROOT)).ordinal());
queryWrapper.eq("open_id", uid);
UserBinding userBinding = this.userBindingMapper.selectOne(queryWrapper);
if (userBinding == null) {
return null;
}
return this.convert(
this.findUser(userBinding.getUserId())
);
}
/**
* 创建并获取第三方用户,相当于第三方登录成功后,将授权关系保存到数据库(开发者业务系统中 social user -> sys user 的绑定关系)
*
* @param userInfo JustAuth 中的 AuthUser
* @return JapUser
*/
@Override
public JapUser createAndGetSocialUser(Object userInfo) {
AuthUser authUser = (AuthUser) userInfo;
// 对 WeChat 系列产品的用户进行特殊处理
if(authUser.getSource().toUpperCase(Locale.ROOT).startsWith(JapUserServiceImpl.WE_CHAT_SOURCE_PREFIX)) {
String unionId = authUser.getRawUserInfo().getString("unionId");
if(unionId != null) {
authUser.setUuid(unionId);
}
}
// 查询绑定关系,确定当前用户是否已经登录过业务系统
JapUser user = this.getByPlatformAndUid(authUser.getSource(), authUser.getUuid());
if (user == null) {
// 判断用户是否登录
user = (JapUser) Context.current().session("_jap:session:user");
if (user == null) {
return null;
}
user.setAdditional(authUser);
// 添加用户
this.userBindingMapper.insert(UserBinding.builder()
.userId(Long.valueOf(user.getUserId()))
.platform(AuthDefaultSource.valueOf(authUser.getSource().toUpperCase(Locale.ROOT)).ordinal())
.openId(authUser.getUuid())
.metadata(authUser.getRawUserInfo())
.build()
.buildDate());
}
return user;
}
private JapUser convert(User user) {
if (user == null) {
return null;
}
return new JapUser()
.setUserId(String.valueOf(user.getId()))
.setUsername(user.getUsername())
.setPassword(user.getPassword())
.setAdditional(user);
}
}
在上述服务实现后,你还需要通过 Solon 的 Aop 将实现注入进去:
Just Auth Plus 本身具使用 ServiceLoader 加载数据的能力,但是由于 Solon 的类加载机制在某些情况下可能不能实现加载,所以建议使用一定会成功的 Aop 注入方式。
由于 JapMfaService 注入名称为 JapMfaService,所以不能直接将 JapMfaServcieImpl 当做名称注入进去。
/**
* @author 颖
*/
@Configuration
public class JapConfig {
@Bean
public void core() {
JapMfaServiceImpl japMfaService = Solon.context().getBeanOrNew(JapMfaServiceImpl.class);
Solon.context().wrapAndPut(JapMfaService.class, japMfaService);
}
}
内置 Controller 概览
你可以在每一个 Just Auth Plus 请求后携带参数 next={} 来标明这次请求不属于前后端分离的请求,相关操作完成后将会跳转到 next 指定的地址而不是直接返回 JSON 数据。Next 地址白名单配置见上方配置文件详解。!!! /auth/social/{platform} !!! 请求必须携带 next 参数用于第三方回调后的跳转。
在每一次请求跳转后,你可以使用 Context.current().session(JAP_LAST_RESPONSE_KEY);,获取该请求中上一个产生的 JapResponse。
POST /auth/login
GET/POST /auth/social/{platform}
GET /account/current
GET /auth/mfa/generate
POST /auth/mfa/verify