jap-ids-solon-plugin
此插件,由社区成员(浅念)贡献
<dependency>
<groupId>org.noear</groupId>
<artifactId>jap-ids-solon-plugin</artifactId>
</dependency>
1、概述
Just Auth Plus Identity Server 向外为 Solon 提供了实现 OAuth 2.0 服务的能力。
2、配置示例
jap:
# Just Auth Plus Identity Server 配置
ids:
# 全部控制器注册的根路径,默认为 /oauth
basePath: /auth/o
# IdsConfig 映射
config:
# 服务根路径,用于 服务发现
issuer: http://127.0.0.1:6040
# Jwt 密钥配置
jwtConfig:
jwksKeyId: jap-jwk-keyid
jwksJson: |-
{
"keys": [
{
"p": "v5G0QPkr9zi1znff2g7p5K1ac1F2KNjXmk31Etl0UrRBwHiTxM_MkkldGlxnXWoFL4_cPZZMt_W14Td5qApknLFOh9iRWRPwqlFgC-eQzUjPeYvxjRbtV5QUHtbzrDCLjLiSNyhsLXHyi_yOawD2BS4U6sBWMSJlL2lShU7EAaU",
"kty": "RSA",
"q": "s2X9UeuEWky_io9hFAoHZjBxMBheNAGrHXtWat6zlg2tf_SIKpZ7Xs8C_-kr9Pvj-D428QsOjFZE-EtNBSXoMrvlMk7fGDl9x1dHvLS9GSitkXH2-Wthg8j0j0nfAmyEt94jP-XEkYic1Ok7EfBOPuvL21HO7YuB-cOff9ZGvBk",
"d": "Rj-QBeBdx85VIHkwVY1T94ZeeC_Z6Zw-cz5lk5Msw0U9QhSTWo28-d2lYjK7dhQn-E19JhTbCVE11UuUqENKZmO__yRgO1UJaj2x6vWMtgJptah7m8lI-QW0w6TnVxAHWfRPpKSEfbN4SpeufYf5PYhmmzT0A954Z2o0kqS4iHd0gwNAovOXaxriGXO1CcOQjBFEcm0BdboQZ7CKCoJ1D6S0xZpVFSJg-1AtagY5dzStyekzETO2tQSmVw4ogIoJsIbu3aYwbukmCoULQfJ36D0mPzrTG5oocEbbuCps_vH72VjZORHHAl4hwritFT_jD2bdQHSNMGukga8C0L1WQQ",
"e": "AQAB",
"use": "sig",
"kid": "jap-jwk-keyid",
"qi": "Asr5dZMDvwgquE6uFnDaBC76PY5JUzxQ5wY5oc4nhIm8UxQWwYZTWq-HOWkMB5c99fG1QxLWQKGtsguXfOXoNgnI--yHzLZcXf1XAd0siguaF1cgQIqwRUf4byofE6uJ-2ZON_ezn6Uvly8fDIlgwmKAiiwWvHI4iLqvqOReBgs",
"dp": "oIUzuFnR6FcBqJ8z2KE0haRorUZuLy38A1UdbQz_dqmKiv--OmUw8sc8l3EkP9ctvzvZfVWqtV7TZ4M3koIa6l18A0KKEE0wFVcYlwETiaBgEWYdIm86s27mKS1Og1MuK90gz800UCQx6_DVWX41qAOEDWzbDFLY3JBxUDi-7u0",
"alg": "RS256",
"dq": "MpNSM0IecgapCTsatzeMlnaZsmFsTWUbBJi86CwYnPkGLMiXisoZxcS-p77osYxB3L5NZu8jDtVTZFx2PjlNmN_34ZLyujWbDBPDGaQqm2koZZSnd_GZ8Dk7GRpOULSfRebOMTlpjU3iSPPnv0rsBDkdo5sQp09pOSy5TqTuFCE",
"n": "hj8zFdhYFi-47PO4B4HTRuOLPR_rpZJi66g4JoY4gyhb5v3Q57etSU9BnW9QQNoUMDvhCFSwkz0hgY5HqVj0zOG5s9x2a594UDIinKsm434b-pT6bueYdvM_mIUEKka5pqhy90wTTka42GvM-rBATHPTarq0kPTR1iBtYao8zX-RWmCbdumEWOkMFUGbBkUcOSJWzoLzN161WdYr2kJU5PFraUP3hG9fPpMEtvqd6IwEL-MOVx3nqc7zk3D91E6eU7EaOy8nz8echQLl6Ps34BSwEpgOhaHDD6IJzetW-KorYeC0r0okXhrl0sUVE2c71vKPVVtueJSIH6OwA3dVHQ"
}
]
}
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);
}
}
相关服务具体实现
/**
* @author 颖
* @version 1.0.0
* @date 2021-04-14 10:27
* @since 1.0.0
*/
public class IdsClientDetailServiceImpl implements IdsClientDetailService {
@Db
OAuthClientMapper oAuthClientMapper;
/**
* 通过 client_id 查询客户端信息
*
* @param clientId 客户端应用id
* @return AppOauthClientDetails
*/
@Override
public ClientDetail getByClientId(String clientId) {
QueryWrapper<OAuthClient> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("client_id", clientId);
return this.convert(
this.oAuthClientMapper.selectOne(queryWrapper)
);
}
/**
* Add client
*
* @param clientDetail Client application details
* @return ClientDetail
*/
@Override
public ClientDetail add(ClientDetail clientDetail) {
return IdsClientDetailService.super.add(clientDetail);
}
/**
* Modify the client
*
* @param clientDetail Client application details
* @return ClientDetail
*/
@Override
public ClientDetail update(ClientDetail clientDetail) {
return IdsClientDetailService.super.update(clientDetail);
}
/**
* Delete client by primary key
*
* @param id Primary key of the client application
* @return boolean
*/
@Override
public boolean removeById(String id) {
this.oAuthClientMapper.deleteById(Long.parseLong(id));
return true;
}
/**
* Delete client by client id
*
* @param clientId Client application id
* @return ClientDetail
*/
@Override
public boolean removeByClientId(String clientId) {
QueryWrapper<OAuthClient> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("client_id", clientId);
this.oAuthClientMapper.delete(queryWrapper);
return true;
}
/**
* 获取所有 client detail
*
* @return List
*/
@Override
public List<ClientDetail> getAllClientDetail() {
return this.oAuthClientMapper.selectList(new QueryWrapper<>())
.stream()
.map(this::convert)
.collect(Collectors.toList());
}
private ClientDetail convert(OAuthClient client) {
if(client == null) {
return null;
}
return new ClientDetail()
.setId(String.valueOf(client.getId()))
.setAppName(client.getName())
.setClientId(client.getClientId())
.setClientSecret(client.getClientSecret())
.setSiteDomain(client.getSiteDomain())
.setRedirectUri(client.getRedirectUri())
.setLogoutRedirectUri(client.getLogoutUri())
.setLogo(client.getLogo())
.setAvailable(client.isAvailable())
.setDescription(client.getDescription())
.setScopes(client.getScopes())
.setGrantTypes(GrantType.values()[client.getGrantTypes()].getType())
.setResponseTypes(ResponseType.values()[client.getResponseTypes()].getType())
.setCodeExpiresIn(client.getCodeExpiresIn())
.setIdTokenExpiresIn(client.getIdTokenExpiresIn())
.setAccessTokenExpiresIn(client.getAccessTokenExpiresIn())
.setRefreshTokenExpiresIn(client.getRefreshTokenExpiresIn())
.setAutoApprove(client.getAutoApprove())
.setEnablePkce(client.getEnablePkce())
.setCodeChallengeMethod(client.getCodeChallengeMethod());
}
}
/**
* @author 颖
* @version 1.0.0
* @date 2021-04-16 16:32
* @since 1.0.0
*/
public class IdsIdentityServiceImpl implements IdsIdentityService {
/**
* Get the jwt token encryption key string
*
* @param identity User/organization/enterprise identification
* @return Encryption key string in json format
*/
@Override
public String getJwksJson(String identity) {
return IdsIdentityService.super.getJwksJson(identity);
}
/**
* Get the configuration of jwt token encryption
*
* @param identity User/organization/enterprise identification
* @return Encryption key string in json format
*/
@Override
public JwtConfig getJwtConfig(String identity) {
return IdsIdentityService.super.getJwtConfig(identity);
}
}
/**
* @author 颖
* @version 1.0.0
* @date 2021-04-14 10:27
* @since 1.0.0
*/
public class IdsUserServiceImpl extends JapService implements IdsUserService {
/**
* Login with account and password
*
* @param username account number
* @param password password
* @return UserInfo
*/
@Override
public UserInfo loginByUsernameAndPassword(String username, String password, String clientId) {
User user = this.findUser(username);
if (user != null) {
if (user.authenticate(password)) {
return this.convert(user);
}
}
return null;
}
/**
* Get user info by userid.
*
* @param userId userId of the business system
* @return UserInfo
*/
@Override
public UserInfo getById(String userId) {
return this.convert(
this.findUser(Long.parseLong(userId))
);
}
/**
* Get user info by username.
*
* @param username username of the business system
* @return UserInfo
*/
@Override
public UserInfo getByName(String username, String clientId) {
return this.convert(
this.findUser(username)
);
}
private UserInfo convert(User user) {
if (user == null) {
return null;
}
return new UserInfo()
.setId(String.valueOf(user.getId()))
.setUsername(user.getUsername())
.setEmail(user.getEmail());
}
}
在上述服务实现后,你还需要通过 Solon 的 Aop 将实现注入进去:
Just Auth Plus 本身具使用 ServiceLoader 加载数据的能力,但是由于 Solon 的类加载机制在某些情况下可能不能实现加载,所以建议使用一定会成功的 Aop 注入方式。
/**
* @author 颖
*/
@Configuration
public class JapConfig {
@Bean
public void ids(@Inject IdsContext context) {
context.setCache(Solon.context().getBeanOrNew(IdsCacheImpl.class));
context.setClientDetailService(Solon.context().getBeanOrNew(IdsClientDetailServiceImpl.class));
context.setIdentityService(Solon.context().getBeanOrNew(IdsIdentityServiceImpl.class));
context.setUserService(Solon.context().getBeanOrNew(IdsUserServiceImpl.class));
}
}
内置 Controller 概览
你可以在每一个 Just Auth Plus 请求后携带参数 next={} 来标明这次请求不属于前后端分离的请求,相关操作完成后将会跳转到 next 指定的地址而不是直接返回 JSON 数据。Next 地址白名单配置见上方配置文件详解。!!! /auth/social/{platform} !!! 请求必须携带 next 参数用于第三方回调后的跳转。
在每一次请求跳转后,你可以使用 Context.current().session(JAP_LAST_RESPONSE_KEY);,获取该请求中上一个产生的 JapResponse。
Just Auth Plus Identity Server
GET 服务发现:/.well-known/openid-configuration
GET 解密公钥:/.well-known/jwks.json
GET 获取授权:/oauth/authorize 跳转页面(登录页面或者回调页面)
POST 同意授权:/oauth/authorize 同意授权(在确认授权之后)
GET 自动授权:/oauth/authorize/auto 自动授权(不会显示确认授权页面)
GET 确认授权:/oauth/confirm 登录完成后的确认授权页面
GET/POST 获取/刷新Token:/oauth/token
GET/POST 收回Token:/oauth/revoke_token
GET/POST 用户详情:/oauth/userinfo
GET check session:/oauth/check_session
GET 授权异常:/oauth/error
GET 登录:/oauth/login 跳转到登录页面
POST 登录:/oauth/login 执行登录表单
GET 退出登录:/oauth/logout