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);
}
}