如何使用JWT实现特定端点的请求身份验证机制


  1. 如何使用JWT实现特定端点的请求身份验证机制。请注意,并不是所有的请求都经过了身份验证,但应该可以以某种方式指定请求是否应该包含身份验证令牌。如何实现代币续订?在令牌过期的情况下,服务器返回HTTP状态代码300,并且可以使用当前(过期(JWT令牌来请求新的令牌。实施的解决方案应可重复使用

有趣的是,我不得不在一个可能的项目中尝试这一点,我提出了两个解决方案,但我不会发布一个。

我最终做的是创建一个接口,提供所有必需的字段。

像低于

interface IClient {
/**
* Defines name of theClass (Used in shared preferences to make a store of creds)
* @return String
*/
@NonNull
String getName();
/**
* Defines endpoint for Redirection
* @return String
*/
@NonNull String getState();
/**
* Defines a URI to start a web view etc.
* @return String
*/
@NonNull
Uri getIntentUri();

/**
* Defines endpoint for Access token
* @return Endpoint
*/
@NonNull EndPoint getTokenEndpoint();
/**
* Defines endpoint for Access Code
* @return Endpoint
*/
@NonNull EndPoint getAutenticationCodeEndPoint();
/**
* Defines endpoint for getting the User Information
* @return Endpoint
*/
@NonNull EndPoint getUserInfoEndpoint();
/**
* Defines uri for Redirection
* @return String
*/
@NonNull String getRedirectUri();
/**
* Defines GrantType
* @return GrantType
*/
@NonNull GrantType getGrantType();
/**
* Defines clientId
* @return String
*/
@NonNull String getClientId();
/**
* Defines clientSecret
* @return String
*/
@NonNull String getClientSecret();
/**
* Defines scope
* @return String
*/
@NonNull String getScope();
@NonNull Class<?> logInActivity();
@NonNull Class<?> logOutActivity();
void logOut();
@LayoutRes int intermediaryLoginView();
void startLogin(Context context);
}

然后你什么时候把它实现到一个类中

public class Client implements IClient {
@NotNull
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@NotNull
@Override
public String getState() {
return "DEMO";
}
@NonNull
@Override
public Uri getIntentUri() {
return Uri.parse("https://exampleurl" +
"/authorize?client_id=" + getClientId() +
"&scope=" + getScope() +
"&state=" + getState() +
"&redirect_uri=" + getRedirectUri() +
"&response_type=code"
);
}

@NotNull
@Override
public EndPoint getTokenEndpoint() {
return new EndPoint("https://url", "token");
}
@NotNull
@Override
public EndPoint getAutenticationCodeEndPoint() {
return new EndPoint("https://url", "token");
}
@NotNull
@Override
public EndPoint getUserInfoEndpoint() {
return new EndPoint("https://url", "/api/me");
}
@NotNull
@Override
public GrantType getGrantType() {
return GrantType.authorization_code;
}
@NotNull
@Override
public String getClientId() {
return "";
}
@NotNull
@Override
public String getClientSecret() {
return "";
}
@NotNull
@Override
public String getRedirectUri() {
return "example://oauth";
}
@NotNull
@Override
public String getScope() {
return "app.something.user";
}
@NotNull
@Override
public Class<?> logInActivity() {
return MainActivity.class;
}
@LayoutRes
@Override
public int intermediaryLoginView(){
return R.layout.logginin;
}
@NotNull
@Override
public Class<?> logOutActivity() {
return Loggedout.class;
}
@Override
public void logOut() {
}
@Override
public void startLogin(Context context){
CustomTabsIntent.Builder customTabsIntentBuilder = new CustomTabsIntent.Builder();
customTabsIntentBuilder.setShowTitle(false);
customTabsIntentBuilder.enableUrlBarHiding();
CustomTabsIntent intents = customTabsIntentBuilder.build();
Log.d(this.getClass().getSimpleName(),  getIntentUri().toString());
intents.launchUrl(context, getIntentUri());
}
}

下一部分将真正取决于您的实现方式

我最终创建了一个OAuth管理器类

public class OAuthManager {

private IClient iClient;
private static OAuthSharedPreferences mOAuthSharedPreferences;
private static OAuthInterceptor mOAuthInterceptor;

private InternalOAuth2Provider internalOAuth2Provider;

private static Observable<AccessTokenResponse> tokenResponseObservable;

RetrofitApi retrofitApi;

public OAuthManager(InternalOAuth2Provider internalOAuth2Provider, IClient iClient, SharedPreferences mSharedPreferences) {
this.iClient = iClient;
this.internalOAuth2Provider = internalOAuth2Provider;
mOAuthSharedPreferences = new OAuthSharedPreferences(mSharedPreferences);
mOAuthInterceptor = new OAuthInterceptor(mOAuthSharedPreferences);


OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();

Retrofit builder = new Retrofit.Builder()
.baseUrl("https://this-is-replaced-regardless.com/")
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create()).build();

retrofitApi = builder.create(RetrofitApi.class);

initRefreshTokenObservable();//try to Refresh token immediately <-- log user in if needed
}

private void initRefreshTokenObservable() {
tokenResponseObservable = Single.defer(this::refreshAccessToken)
.toObservable()
.share()
.doOnComplete(() -> internalOAuth2Provider.SuccessfulLogin());//<---TODO this or  mInternalOAuth2Provider.SuccessfulLogin(); ?????

}

private Single<AccessTokenResponse> refreshAccessToken() {
return getRefreshToken()
.observeOn(Schedulers.io())
.doOnSuccess(
accessTokenResponse -> mOAuthSharedPreferences.UpdateOAuthCredentials(accessTokenResponse)
).doOnError(throwable -> {
if (OAuthAuthenticationErrorHandler.invalidRefreshToken(throwable)) {
logOut();
}
});
}


void logOut(){
if(mOAuthSharedPreferences.logout()){
internalOAuth2Provider.SuccessfulLogout();
return;
}
Log.d("OAuthManager - Internal", "Unable to clear the saved preferences");
}

static Observable<?> wrapWithOAuth(Observable<?> observable) {
return observable.compose(observableTransformer());
}

static <T> ObservableTransformer<T, T> observableTransformer() {
return upstream -> {
upstream.onErrorResumeNext(throwable -> {
if (mOAuthSharedPreferences.isTokenExpired()) {
tokenResponseObservable.flatMap(accessTokenResponse -> upstream);
} else {
upstream.onErrorResumeNext((Function<Throwable, ObservableSource<T>>) throwable1 -> {
if (OAuthAuthenticationErrorHandler.invalidAccessToken(throwable1)) {
tokenResponseObservable.flatMap(accessTokenResponse -> upstream);
} else {
return Observable.error(throwable1);
}
return null;
});
}
});
return null;
};
}

/**
* Okhttp synchronous request to refresh token
*
* @return Single<AccessTokenResponse>
*/
private Single<AccessTokenResponse> getRefreshToken() {

if (mOAuthSharedPreferences.getRefreshToken().length() > 0 && mOAuthSharedPreferences.getExpirationTime() < System.currentTimeMillis() / 1000) {
return null;
}

RefreshTokenBody body = new RefreshTokenBody(
iClient.getClientId(),
iClient.getClientSecret(),
iClient.getGrantType().toString(),
mOAuthSharedPreferences.getRefreshToken()
);

return retrofitApi.refreshToken(iClient.getTokenEndpoint().getUri(), body);
}

/**
* Okhttp synchronous request to refresh token
*
* @return Response
*/
private Single<AccessTokenResponse> authenticateAuthorizationCode(String code) {

if (mOAuthSharedPreferences.getRefreshToken().length() > 0 && mOAuthSharedPreferences.getExpirationTime() < System.currentTimeMillis() / 1000) {
return null;
}

return retrofitApi.authenticateAuthorizationCode2(iClient.getAutenticationCodeEndPoint().getUri(), iClient.getClientId(),
iClient.getClientSecret(),
code,
iClient.getGrantType().toString(),
iClient.getRedirectUri());
}

public void validateAuthorizationGrant(String code) {
if (code == null || code.length() < 1) {
return;
}
// Network Request to login
authenticateAuthorizationCode(code).observeOn(Schedulers.io()).subscribeOn(Schedulers.io())
.doOnSuccess(accessTokenResponse -> internalOAuth2Provider.SuccessfulLogin())
.doOnError(Throwable::printStackTrace)
.subscribe();
}

/**
* Interceptor that adds Authorisation header with access token
*/
public static OAuthInterceptor getOAuthInterceptor() {
if (mOAuthInterceptor == null) {
mOAuthInterceptor = new OAuthInterceptor(mOAuthSharedPreferences);
}
return mOAuthInterceptor;
}
}

在扩展应用程序的Base应用程序中,我将创建一个内部OAuth提供者的实例

public class InternalOAuth2Provider {
private static InternalOAuth2Provider _sInstance;
public OkHttpClient client;
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
Gson gson = new Gson();
OAuthManager oAuthManager;
private Context mContext;
private IClient iClient;
public synchronized static InternalOAuth2Provider getInstance(IClient iClient, Context context) {
if (_sInstance == null) _sInstance = new InternalOAuth2Provider(iClient, context);
return _sInstance;
}
protected InternalOAuth2Provider(IClient iClient, Context context) {
client = new OkHttpClient();
SharedPreferences mSharedPreferences = context.getSharedPreferences(iClient.getName(), MODE_PRIVATE);
oAuthManager = new OAuthManager(this, iClient, mSharedPreferences);
this.mContext = context;
this.iClient = iClient;
init();
_sInstance = this;
}
protected void init() {
List<PackageInfo> pInfos = mContext.getPackageManager().getInstalledPackages(PackageManager.GET_ACTIVITIES);
boolean loginClass = false;
boolean logOutClass = false;
for (PackageInfo pInfo : pInfos) {
ActivityInfo[] aInfos = pInfo.activities;
if (aInfos != null) {
for (ActivityInfo activityInfo : aInfos) {
if (activityInfo.name.equals(iClient.logInActivity().getName())) {
loginClass = true;
}
if (activityInfo.name.equals(iClient.logOutActivity().getName())) {
logOutClass = true;
}
}
}
}
if (!loginClass || !logOutClass) {
throw new IllegalArgumentException("Invalid return value Activity Class<" + (loginClass ? iClient.logOutActivity().getSimpleName() : iClient.logInActivity().getSimpleName()) + ".class> not found in Manifest");
}
}
@Nullable
public UserInfo getUserInformation() {
try {
RequestBody body = RequestBody.create("", JSON);
Request request = new Request.Builder()
.url(
iClient.getUserInfoEndpoint().getUri()
)
.post(body)
.build();
Response response = client.newCall(request).execute();
return parseResponseIntoUserInfo(response.body().string());
} catch (Exception ignored) {
return null;
}
}
public OAuthManager getoAuthManager() {
return oAuthManager;
}
private UserInfo parseResponseIntoUserInfo(String responseBody) {
return gson.fromJson(responseBody, UserInfo.class);
}
public void logOut(){
oAuthManager.logOut();
}
public void SuccessfulLogin() {
Intent intent = new Intent(mContext, iClient.logInActivity());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
public void SuccessfulLogout() {
Intent intent = new Intent(mContext, iClient.logOutActivity());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
}
public void getUriIntent(Uri data) {
if (iClient.getGrantType() != authorization_code) {
return;
}
if (data == null) {
return;
}
String code = data.getQueryParameter("code") == null ? "" : data.getQueryParameter("code");
//TODO is this the only one we receive URI data for?
switch (iClient.getGrantType()) {
case authorization_code:
oAuthManager.validateAuthorizationGrant(code);
break;
case password_grant:
case device_code:
case PKCE:
case client_credentials:
default:
}
}
public void Login(Context context){
iClient.startLogin(context);
}
}

如果你正在使用改装,你需要提供一个拦截器,这将把承载令牌添加到你的请求

class OAuthInterceptor implements Interceptor {
private OAuthSharedPreferences oAuthSharedPreferences;
public OAuthInterceptor(OAuthSharedPreferences oAuthSharedPreferences) {
this.oAuthSharedPreferences = oAuthSharedPreferences;
}
@NotNull
@Override
public Response intercept(@NotNull Interceptor.Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder = request.newBuilder();
if(!TextUtils.isEmpty(oAuthSharedPreferences.getAuthenticationToken())){
String accessToken = oAuthSharedPreferences.getAuthenticationToken();
builder.addHeader("Authorization", "Bearer " + accessToken);
}
return chain.proceed(builder.build());
}
}

然后,为了包装您的调用并排除OAuth未保护的调用,我创建了一个CallAdapterFactory(注意,我自己还没有实现(

class OAuthCallAdapterFactory extends CallAdapter.Factory{
@Nullable
@Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
Type rawReturnType = CallAdapter.Factory.getRawType(returnType);
boolean ignored = false;
for (Annotation annotation : annotations) {
if (annotation instanceof AuthExempt) {
ignored = true;
break;
}
}
if (rawReturnType.equals(Completable.class)) {
return new OAuthCallAdapter<Void>(Void.class, true, true, false, false, false, true, ignored);
}
boolean isFlowable = rawReturnType == Flowable.class;
boolean isSingle = rawReturnType == Single.class;
boolean isMaybe = rawReturnType == Maybe.class;
if (rawReturnType != Observable.class && !isFlowable && !isSingle && !isMaybe) {
return null;
}
boolean isBody = false;
Type responseType;
if (!(returnType instanceof ParameterizedType)) {
throw new IllegalStateException(returnType.toString() + " return type must be parameterized"
+ " as " + returnType.toString() + "<Foo> or " + returnType.toString() + "<? extends Foo>");
}
Type observableType = CallAdapter.Factory.getParameterUpperBound(0, (ParameterizedType)returnType);
Type rawObservableType = CallAdapter.Factory.getRawType(observableType);
if (rawObservableType == Response.class) {
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalStateException("Response must be parameterized as Response<Foo> or Response<? extends Foo>");
}
responseType = CallAdapter.Factory.getParameterUpperBound(0, (ParameterizedType)observableType);
} else {
responseType = observableType;
isBody = true;
}
return new OAuthCallAdapter<>(responseType, true, isBody, isFlowable, isSingle, isMaybe, false, ignored);
}
}

类OAuthCallAdapter实现CallAdapter<R、 对象>{

private final Type responseType;
private final boolean isAsync;
private final boolean isBody;
private final boolean isFlowable;
private final boolean isSingle;
private final boolean isMaybe;
private final boolean isCompletable;
private final boolean ignoreAuth;
public OAuthCallAdapter(Type responseType, boolean isAsync, boolean isBody, boolean isFlowable, boolean isSingle, boolean isMaybe, boolean isCompletable, boolean ignoreAuth) {
this.responseType = responseType;
this.isAsync = isAsync;
this.isBody = isBody;
this.isFlowable = isFlowable;
this.isSingle = isSingle;
this.isMaybe = isMaybe;
this.isCompletable = isCompletable;
this.ignoreAuth = ignoreAuth;
}
@NotNull
@Override
public Type responseType() {
return responseType;
}
@NotNull
@Override
public Object adapt(@NotNull Call<R> call) {
Observable<Response<R>> responseObservable = isAsync
? new CallEnqueueObservable<>(call)
: new CallExecuteObservable<>(call);
Observable<?> observable;
if (isBody) {
observable = new BodyObservable<>(responseObservable);
} else {
observable = responseObservable;
}
if (!ignoreAuth) {
observable = wrapWithOAuth(observable);
}
if (isFlowable) {
return observable.toFlowable(BackpressureStrategy.LATEST);
}
if (isSingle) {
return observable.singleOrError();
}
if (isMaybe) {
return observable.singleElement();
}
if (isCompletable) {
return observable.ignoreElements();
}
return RxJavaPlugins.onAssembly(observable);
}

}类OAuthCallAdapter实现CallAdapter<R、 对象>{

private final Type responseType;
private final boolean isAsync;
private final boolean isBody;
private final boolean isFlowable;
private final boolean isSingle;
private final boolean isMaybe;
private final boolean isCompletable;
private final boolean ignoreAuth;
public OAuthCallAdapter(Type responseType, boolean isAsync, boolean isBody, boolean isFlowable, boolean isSingle, boolean isMaybe, boolean isCompletable, boolean ignoreAuth) {
this.responseType = responseType;
this.isAsync = isAsync;
this.isBody = isBody;
this.isFlowable = isFlowable;
this.isSingle = isSingle;
this.isMaybe = isMaybe;
this.isCompletable = isCompletable;
this.ignoreAuth = ignoreAuth;
}
@NotNull
@Override
public Type responseType() {
return responseType;
}
@NotNull
@Override
public Object adapt(@NotNull Call<R> call) {
Observable<Response<R>> responseObservable = isAsync
? new CallEnqueueObservable<>(call)
: new CallExecuteObservable<>(call);
Observable<?> observable;
if (isBody) {
observable = new BodyObservable<>(responseObservable);
} else {
observable = responseObservable;
}
if (!ignoreAuth) {
observable = wrapWithOAuth(observable);
}
if (isFlowable) {
return observable.toFlowable(BackpressureStrategy.LATEST);
}
if (isSingle) {
return observable.singleOrError();
}
if (isMaybe) {
return observable.singleElement();
}
if (isCompletable) {
return observable.ignoreElements();
}
return RxJavaPlugins.onAssembly(observable);
}

}

和一个自定义错误处理程序来刷新令牌

class OAuthAuthenticationErrorHandler {
public static boolean invalidAccessToken(Throwable throwable) {
return throwable instanceof HttpException && ((HttpException) throwable).code() == HttpURLConnection.HTTP_UNAUTHORIZED;
}
public static boolean invalidRefreshToken(Throwable throwable) {
if(throwable instanceof HttpException){
return ((HttpException) throwable).code() == HttpURLConnection.HTTP_BAD_REQUEST || ((HttpException) throwable).code() == HttpURLConnection.HTTP_UNAUTHORIZED;
}
return false;
}
}

这里我的注释类排除登录点

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface AuthExempt {
}

通过更多的实验,您可以从Json文件加载一些值并填充类

class RxOAuthUser {
private String client_id;
private String client_secret;
private String redirect_uri;
private String authorization_scope;
private String oAuthEndpoint;
private Long expirationTime;
private String refreshToken;
private String accessToken;
private String grantType;
RxOAuthUser(Context mContext) {
try {
InputStream inputStream = mContext.getAssets().open(mContext.getPackageName() + ".json");
int size = inputStream.available();
byte[] buffer = new byte[size];
if (inputStream.read(buffer) != size) {
throw new IndexOutOfBoundsException();
}
inputStream.close();
JSONObject jsonObject = new JSONObject(new String(buffer, StandardCharsets.UTF_8));
client_id = jsonObject.getString("client_id");
client_secret = jsonObject.getString("client_secret");
redirect_uri = jsonObject.getString("redirect_uri");
authorization_scope = jsonObject.getString("scope");
oAuthEndpoint = jsonObject.getString("query_url");
grantType = jsonObject.getString("grant_type");
} catch (Exception e) {
e.printStackTrace();
}
}

这在很大程度上受到了https://github.com/AckeeCZ/rxoauth-一个比我聪明得多的人-所以任何反馈都会受到的赞赏

最新更新