pac4j扩展ruoyi oauth登录思路

pac4j是一个基础的安全框架 它实现了各种类型的安全认证手段,这里主要讲集成oauth2.0 若依版本的思路

我们需要手动扩展一个ruoyiclient模仿wechatclient就行

在下面这个代码中我们可以看到客户端初始化过程需要添加

authUrl、tokenUrl、对应权限Scope以及两个类分别是RuoYiApi20、RuoYiProfileDefinition
@Setter
@Getter
public class RuoyiClient extends OAuth20Client {

    public enum RuoyiScope {
        USER_READ("user.read"),

        USER_WRITE("user.write");
        private final String permission;
        RuoyiScope(String permission) {
            this.permission = permission;
        }
        public String getPermission() {
            return permission;
        }
    }

    protected List<RuoyiClient.RuoyiScope> scopes;


    public RuoyiClient(final String key, final String secret) {
        setKey(key);
        setSecret(secret);
    }

    @Override
    protected void clientInit() {
        String authUrl = "http://127.0.0.1:80/sso";
        String tokenUrl = "http://127.0.0.1:48080/admin-api/system/oauth2/token";
      
        RuoYiApi20 ruoYiApi20 = new RuoYiApi20(authUrl, tokenUrl);
        ruoYiApi20.setClientAuthenticationMethod("requestBody");
        configuration.setApi(ruoYiApi20);
        configuration.setScope(getOAuthScope());
        configuration.setProfileDefinition(new RuoYiProfileDefinition());
//        configuration.setWithState(true);
        defaultProfileCreator(new RuoYiProfileCreator(configuration, this));
        super.clientInit();
    }


    protected String getOAuthScope() {
        StringBuilder builder = null;
        if (scopes == null || scopes.isEmpty()) {
            scopes = new ArrayList<>();
            scopes.add(RuoyiScope.USER_READ);
            scopes.add(RuoyiScope.USER_WRITE);
        }
        for (RuoyiScope value : scopes) {
            if (builder == null) {
                builder = new StringBuilder();
            } else {
                builder.append(",");
            }
            builder.append(value.getPermission());
        }
        return builder.toString();
    }

    public void addScope(RuoyiClient.RuoyiScope scopes) {
        if (this.scopes == null) {
            this.scopes = new ArrayList<>();
        }
        this.scopes.add(scopes);
    }
}

首先先看RuoYiApi20的代码很简单,只需要继承GenericApi20

重写一个方法getAccessTokenExtractor,这个方法用来初始化一个解析ruoyi获取token接口返回数据的的body的类,因为默认的实现是OAuth2AccessTokenJsonExtractor类 这个类解析body的时候直接在第一层开始解析找需要的值如图2图3

public class RuoYiApi20 extends GenericApi20 {


    public RuoYiApi20(String authUrl, String tokenUrl) {
        super(authUrl, tokenUrl);
    }

    @Override
    public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
        return RuoYiJsonExtractor.instance();
    }
}

而若依其实是将这些数据包裹在data中所以我们需要先拨开一层json,以下是RuoYiJsonExtractor 的相关代码,可以看到JsonNode data = bodyNew.get("data");我们拨开了一层数据再解析真正的json数据获得token等数据zzai 

public class RuoYiJsonExtractor extends OAuth2AccessTokenJsonExtractor {

    protected RuoYiJsonExtractor() {
    }

    private static class InstanceHolder {

        private static final RuoYiJsonExtractor INSTANCE = new RuoYiJsonExtractor();
    }

    public static RuoYiJsonExtractor instance() {
        return InstanceHolder.INSTANCE;
    }

    @Override
    public OAuth2AccessToken extract(Response response) throws IOException {
        final String body = response.getBody();
        Preconditions.checkEmptyString(body, "Response body is incorrect. Can't extract a token from an empty string");

        if (response.getCode() != 200) {
            generateError(body);
        }
        final JsonNode bodyNew = OBJECT_MAPPER.readTree(body);
        JsonNode data = bodyNew.get("data");
        return this.createToken(data.toString());
    }
    private OAuth2AccessToken createToken(String rawResponse) throws IOException {

        final JsonNode response = OBJECT_MAPPER.readTree(rawResponse);

        final JsonNode expiresInNode = response.get("expires_in");
        final JsonNode refreshToken = response.get(OAuthConstants.REFRESH_TOKEN);
        final JsonNode scope = response.get(OAuthConstants.SCOPE);
        final JsonNode tokenType = response.get("token_type");

        return createToken(extractRequiredParameter(response, OAuthConstants.ACCESS_TOKEN, rawResponse).asText(),
                tokenType == null ? null : tokenType.asText(), expiresInNode == null ? null : expiresInNode.asInt(),
                refreshToken == null ? null : refreshToken.asText(), scope == null ? null : scope.asText(), response,
                rawResponse);
    }

    @Override
    protected OAuth2AccessToken createToken(String accessToken, String tokenType, Integer expiresIn, String refreshToken, String scope, JsonNode response, String rawResponse) {
        return super.createToken(accessToken, tokenType, expiresIn, refreshToken, scope, response, rawResponse);
    }
}

 再看RuoYiProfileDefinition类RuoYiProfileDefinition构造函数在一开始先转换一下用户信息的格式extractUserProfile其实就是真正解析获取用户信息的方法,和token一样ruoyi包了一层将真正的数据放在data里所以我们需要重写一下方法解套一层

public class RuoYiProfileDefinition extends OAuth20ProfileDefinition<RuoYiProfile, OAuth20Configuration>  {


    private String profileUrl = "http://127.0.0.1:48080/admin-api/system/oauth2/user/get";
    public static final String NICKNAME = "nickname";
    /**
     * Gender, 1 male and 2 female
     */
    public static final String SEX = "sex";

    public static final String PROVINCE = "province";

    public static final String CITY = "city";

    /**
     * User avatar, the last value represents the size of the square avatar (0, 46, 64, 96, 132 values are optional, 0 is 640 * 640
     * square avatar), the item is empty when the user has no avatar
     */
    public static final String AVATAR = "avatar";



    public RuoYiProfileDefinition() {
        Arrays.stream(new String[] { NICKNAME, PROVINCE, CITY})
                .forEach(a -> primary(a, Converters.STRING));
        primary(SEX, new GenderConverter("1", "2"));
        primary(AVATAR, Converters.URL);
    }

    @Override
    public String getProfileUrl(OAuth2AccessToken oAuth2AccessToken, OAuth20Configuration oAuth20Configuration) {
        return this.profileUrl;
    }


    @Override
    public RuoYiProfile extractUserProfile(String body) {
        final RuoYiProfile profile = new RuoYiProfile();
//        需要调试一下看看从哪里开始解析
        final JsonNode json = JsonHelper.getFirstNode(body);
        if (json != null) {
            Integer code = (Integer) JsonHelper.getElement(json, "code");
            if (code != 0) {
                Object errmsg = JsonHelper.getElement(json, "msg");
                throw new OAuthException(errmsg != null ? errmsg.toString() : "error code " + code);
            }
            for (final String attribute : getPrimaryAttributes()) {
                convertAndAdd(profile, PROFILE_ATTRIBUTE, attribute, JsonHelper.getElement(json.get("data"), attribute));
            }
        } else {
            raiseProfileExtractionJsonError(body);
        }
        return profile;
    }
}
RuoYiProfile就是存放用户信息的类
public class RuoYiProfile extends OAuth20Profile {

    private static final long serialVersionUID = 2576512203937798654L;

    @Override
    public String getDisplayName() {
        return (String) getAttribute(WechatProfileDefinition.NICKNAME);
    }

    @Override
    public String getUsername() {
        return (String) getAttribute(WechatProfileDefinition.NICKNAME);
    }

    @Override
    public Gender getGender() {
        return (Gender) getAttribute(WechatProfileDefinition.SEX);
    }


    @Override
    public URI getPictureUrl() {
        return (URI) getAttribute(RuoYiProfileDefinition.AVATAR);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值