Solon

jap-ids-solon-plugin

v2.7.3 </> markdown

此插件,由社区成员(浅念)贡献

<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