提交 624acc9e 编写于 作者: M Mvbbb

Add jap-http-api module

上级 6d0c837c
......@@ -86,18 +86,6 @@ public interface JapUserService {
return null;
}
/**
* After logging in, bind the account of the third-party platform
*
* @param japUser User information of the third-party platform after successful login
* @param bindUserId The user id that needs to be bound, this is a user of the business system, not a user of the third-party platform.
* @return When binding successfully, return {@code true}, otherwise return {@code false}
*/
default boolean bindSocialUser(JapUser japUser, String bindUserId) {
return false;
}
/**
* Save the oauth login user information to the database and return JapUser
* <p>
......@@ -106,11 +94,22 @@ public interface JapUserService {
* @param platform oauth2 platform name
* @param userInfo The basic user information returned by the OAuth platform
* @param tokenInfo The token information returned by the OAuth platform, developers can store tokens
* , type {@code com.fujieid.jap.oauth2.token.AccessToken}
* , type {@code com.fujieid.jap.oauth2.helper.AccessToken}
* @return When saving successfully, return {@code JapUser}, otherwise return {@code null}
*/
default JapUser createAndGetOauth2User(String platform, Map<String, Object> userInfo, Object tokenInfo) {
return null;
}
/**
* Save the http authed user information to the database and return JapUser
* <p>
* It is suitable for the {@code jap-http-api} module
* @param userinfo user information
* @return When saving successfully, return {@code JapUser}, otherwise return {@code null}
*/
default JapUser createAndGetHttpApiUser(Object userinfo){
return null;
}
}
......@@ -37,7 +37,7 @@ public enum JapErrorCode {
MISS_AUTHENTICATE_CONFIG(1005, "AuthenticateConfig is required."),
MISS_ISSUER(1006, "OidcStrategy requires a issuer option."),
MISS_CREDENTIALS(1007, "Missing credentials"),
INVALID_GRANT_TYPE(1008, "The grant type is not supported by the authorization server, or the current client is not authorized for the grant type."),
ERROR_HTTP_API_CONFIG(1008,"http api config error,please check")
;
private final int errroCode;
......
......@@ -19,9 +19,7 @@ import cn.hutool.core.util.ObjectUtil;
import com.baomidou.kisso.security.token.SSOToken;
import com.fujieid.jap.core.JapConst;
import com.fujieid.jap.core.context.JapAuthentication;
import com.fujieid.jap.core.exception.JapException;
import com.fujieid.jap.sso.JapSsoUtil;
import com.xkcoding.json.util.StringUtil;
import java.util.Map;
......@@ -36,26 +34,14 @@ public class JapTokenHelper {
public static void saveUserToken(String userId, String token) {
if (StringUtil.isEmpty(userId)) {
throw new JapException("Failed to save user token, userid cannot be empty.");
}
if (StringUtil.isEmpty(token)) {
throw new JapException("Failed to save user token, user token cannot be empty.");
}
JapAuthentication.getContext().getCache().set(JapConst.USER_TOKEN_KEY.concat(userId), token);
}
public static String getUserToken(String userId) {
if (StringUtil.isEmpty(userId)) {
throw new JapException("Failed to get user token, userid cannot be empty.");
}
return (String) JapAuthentication.getContext().getCache().get(JapConst.USER_TOKEN_KEY.concat(userId));
}
public static void removeUserToken(String userId) {
if (StringUtil.isEmpty(userId)) {
throw new JapException("Failed to remove user token, userid cannot be empty.");
}
JapAuthentication.getContext().getCache().removeKey(JapConst.USER_TOKEN_KEY.concat(userId));
}
......
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jap</artifactId>
<groupId>com.fujieid</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jap-http-api</artifactId>
<version>1.0.2</version>
<groupId>com.fujieid</groupId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.fujieid</groupId>
<artifactId>jap-core</artifactId>
</dependency>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
</dependency>
</dependencies>
</project>
package com.fujieid.jap.httpapi;
import com.fujieid.jap.core.JapUser;
import com.fujieid.jap.core.config.AuthenticateConfig;
import com.fujieid.jap.httpapi.enums.AuthInfoFieldEnum;
import com.fujieid.jap.httpapi.enums.AuthSchemaEnum;
import com.fujieid.jap.httpapi.enums.ForBearerTokenEnum;
import com.fujieid.jap.httpapi.enums.HttpMethodEnum;
import com.fujieid.jap.httpapi.strategy.GetTokenFromResponseStrategy;
import com.fujieid.jap.httpapi.strategy.RequestBodyToJapUserStrategy;
import com.fujieid.jap.httpapi.util.SimpleAuthJsonUtil;
import com.xkcoding.json.JsonUtil;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.util.HashMap;
import java.util.Map;
/**
* Configuration file for http api authorization module
*
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class HttpApiConfig extends AuthenticateConfig {
/**
* Schema for http authorization,defined by third-party system. eg:BASIC、DIGEST、BEARER.
*/
private AuthSchemaEnum authSchema;
/**
* the http method for us to send request to third-party system, which is given by third-party system.
*/
private HttpMethodEnum httpMethod;
/**
* the login url given by the third-party system.
*/
private String loginUrl;
/**
* params/header/body
*/
private AuthInfoFieldEnum authInfoField;
/**
* custom headers will be carried when request third-party system.
*/
private Map<String,String> customHeaders;
/**
* custom params will be carried when request third-party system.
*/
private Map<String,String> customParams;
/**
* custom body will be carried when request third-party system in json format.
*/
private Map<String,String> customBody;
/**
* define this field when authSchema is BEARER
*/
private String bearerTokenIssueUrl;
/**
* if user auth info field is "body", by default, analyze user auth info by json format. ex:
* {
* "username":"admin",
* "password":"123456"
* }
* Developer's system should customize requestBodyToJapUserStrategy to get userinfo form request if user auth info is not this format.
*/
private RequestBodyToJapUserStrategy requestBodyToJapUserStrategy = new RequestBodyToJapUserStrategy(){
@Override
public JapUser decode(HttpServletRequest request) throws Exception {
BufferedReader reader = request.getReader();
StringBuilder body = new StringBuilder();
String str = null;
while ((str = reader.readLine()) != null) {
body.append(str);
}
return JsonUtil.toBean(body.toString(),JapUser.class);
}
};
/**
* As we know Bearer auth need a token, but how to get this token? we should do a pre-auth to get this token, then store this token
* in developer's system. when user request for bearer auth, http-api module will query for user's token in our system.
* This field is used to define how to do pre-auth, how we get the token from third-party serve.
* When authSchema is BEARER you must define this field.
*/
private ForBearerTokenEnum forBearerTokenEnum;
/**
* When do pre-auth for bearer auth, we get the response from third-party server which contains the token.
* Use this strategy to extract the token from response body.
* By default, search for field like "token":"xxxxxxxx" in response.
*/
private GetTokenFromResponseStrategy getTokenFromResponseStrategy = new GetTokenFromResponseStrategy() {
@Override
public String getToken(String body) {
try{
int i1 = body.indexOf("\"token\"")+7;
int i2 = body.indexOf(':', i1);
int i3 = body.indexOf("\"",i2)+1;
int i4 = body.indexOf("\"", i3+1);
return body.substring(i3,i4);
}catch (Exception e){
return null;
}
}
};
public HttpApiConfig() {
}
public HttpApiConfig(HttpApiConfig httpApiConfig,String loginUrl) {
this.authSchema = httpApiConfig.authSchema;
this.httpMethod = httpApiConfig.httpMethod;
this.loginUrl = loginUrl;
this.authInfoField = httpApiConfig.authInfoField;
this.customHeaders = httpApiConfig.customHeaders;
this.customParams = httpApiConfig.customParams;
this.customBody = httpApiConfig.customBody;
this.bearerTokenIssueUrl = httpApiConfig.bearerTokenIssueUrl;
this.requestBodyToJapUserStrategy = httpApiConfig.requestBodyToJapUserStrategy;
this.forBearerTokenEnum = httpApiConfig.forBearerTokenEnum;
this.getTokenFromResponseStrategy = httpApiConfig.getTokenFromResponseStrategy;
}
public AuthSchemaEnum getAuthSchema() {
return authSchema;
}
public HttpApiConfig setAuthSchema(AuthSchemaEnum authSchema) {
this.authSchema = authSchema;
return this;
}
public HttpMethodEnum getHttpMethod() {
return httpMethod;
}
public HttpApiConfig setHttpMethod(HttpMethodEnum httpMethod) {
this.httpMethod = httpMethod;
return this;
}
public String getLoginUrl() {
return loginUrl;
}
public HttpApiConfig setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
return this;
}
public AuthInfoFieldEnum getAuthInfoField() {
return authInfoField;
}
public HttpApiConfig setAuthInfoField(AuthInfoFieldEnum authInfoField) {
this.authInfoField = authInfoField;
return this;
}
public Map<String, String> getCustomHeaders() {
return customHeaders;
}
public HttpApiConfig setCustomHeaders(Map<String, String> customHeaders) {
this.customHeaders = customHeaders;
return this;
}
public RequestBodyToJapUserStrategy getRequestBodyToJapUserStrategy() {
return requestBodyToJapUserStrategy;
}
public HttpApiConfig setRequestBodyToJapUserStrategy(RequestBodyToJapUserStrategy requestBodyToJapUserStrategy) {
this.requestBodyToJapUserStrategy = requestBodyToJapUserStrategy;
return this;
}
public Map<String, String> getCustomParams() {
return customParams;
}
public Map<String, Object> getCustomParamsObjects() {
return new HashMap<>(customParams);
}
public HttpApiConfig setCustomParams(Map<String, String> customParams) {
this.customParams = customParams;
return this;
}
public Map<String, String> getCustomBody() {
return customBody;
}
public HttpApiConfig setCustomBody(Map<String, String> customBody) {
this.customBody = customBody;
return this;
}
public ForBearerTokenEnum getForBearerTokenEnum() {
return forBearerTokenEnum;
}
public HttpApiConfig setForBearerTokenEnum(ForBearerTokenEnum forBearerTokenEnum) {
this.forBearerTokenEnum = forBearerTokenEnum;
return this;
}
public GetTokenFromResponseStrategy getGetTokenFromResponseStrategy() {
return getTokenFromResponseStrategy;
}
public HttpApiConfig setGetTokenFromResponseStrategy(GetTokenFromResponseStrategy getTokenFromResponseStrategy) {
this.getTokenFromResponseStrategy = getTokenFromResponseStrategy;
return this;
}
public String getBearerTokenIssueUrl() {
return bearerTokenIssueUrl;
}
public HttpApiConfig setBearerTokenIssueUrl(String bearerTokenIssueUrl) {
this.bearerTokenIssueUrl = bearerTokenIssueUrl;
return this;
}
}
package com.fujieid.jap.httpapi;
import com.fujieid.jap.core.JapUser;
import com.fujieid.jap.core.JapUserService;
import com.fujieid.jap.core.cache.JapCache;
import com.fujieid.jap.core.config.AuthenticateConfig;
import com.fujieid.jap.core.config.JapConfig;
import com.fujieid.jap.core.exception.JapException;
import com.fujieid.jap.core.result.JapErrorCode;
import com.fujieid.jap.core.result.JapResponse;
import com.fujieid.jap.core.strategy.AbstractJapStrategy;
import com.fujieid.jap.httpapi.enums.AuthSchemaEnum;
import com.fujieid.jap.httpapi.enums.HttpMethodEnum;
import com.fujieid.jap.httpapi.subject.DigestAuthorizationSubject;
import com.fujieid.jap.httpapi.subject.DigestWwwAuthenticateSubject;
import com.fujieid.jap.httpapi.subject.HttpAuthResponse;
import com.fujieid.jap.httpapi.util.*;
import me.zhyd.oauth.utils.Base64Utils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* The strategy for jap-http-api module to request third party system.
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class HttpApiStrategy extends AbstractJapStrategy {
public HttpApiStrategy(JapUserService japUserService, JapConfig japConfig, JapCache japCache) {
super(japUserService, japConfig, japCache);
}
@Override
public JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) {
try {
checkAuthenticateConfig(config, HttpApiConfig.class);
} catch (JapException e) {
e.printStackTrace();
return JapResponse.error(e.getErrorCode(), e.getErrorMessage());
}
HttpApiConfig httpApiConfig = (HttpApiConfig) config;
return this.doAuthenticate(httpApiConfig, request, response);
}
private JapResponse doAuthenticate(HttpApiConfig config, HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
HttpAuthResponse authResponse = null;
JapUser japUser = null;
try {
japUser = getJapUser(servletRequest, config);
if (japUser == null) {
return JapResponse.error(JapErrorCode.MISS_CREDENTIALS);
}
switch (config.getAuthSchema()) {
case basic:
authResponse = doBasicAuth(japUser, config, servletResponse);
break;
case digest:
authResponse = doDigestAuth(japUser,config,servletResponse);
break;
case bearer:
authResponse = doBearerAuth(japUser,config,servletResponse);
break;
default:
break;
}
}catch (JapException e){
return JapResponse.error(e.getErrorCode(),e.getErrorMessage());
} catch (Exception e) {
e.printStackTrace();
}
if (authResponse!=null&&authResponse.isSuccess()) {
if(config.getAuthSchema()== AuthSchemaEnum.basic||config.getAuthSchema()==AuthSchemaEnum.digest){
japUserService.createAndGetHttpApiUser(japUser);
}
return JapResponse.success(authResponse.getBody());
} else {
return JapResponse.error(JapErrorCode.INVALID_PASSWORD);
}
}
private HttpAuthResponse doDigestAuth(JapUser japUser, HttpApiConfig config, HttpServletResponse servletResponse) {
/*
* send a request to third-party server to get a random number and encryption algorithm
* see: https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.1
*/
HttpAuthResponse responseForWWWAuth = HttpAuthUtil.sendRequest(config.getLoginUrl(),
HttpMethodEnum.get,
config.getCustomHeaders(),
config.getCustomParams(),
SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody()));
if(responseForWWWAuth==null||responseForWWWAuth.getHeaders()==null){
return null;
}
HttpAuthResponse result = null;
String wwwAuthenticate = null;
try {
wwwAuthenticate = responseForWWWAuth.getHeaders().get("WWW-Authenticate").get(0).replaceFirst("Digest ", "");
DigestWwwAuthenticateSubject wwwAuthenticateSubject = (DigestWwwAuthenticateSubject) SubjectSerializeUtil.deSerialize(DigestWwwAuthenticateSubject.class, wwwAuthenticate);
String username = japUser.getUsername();
String realm = wwwAuthenticateSubject.getRealm();
String password = japUser.getPassword();
String method = config.getHttpMethod().toString().toUpperCase();
String digestUri = URLUtil.getRelativeUri(config.getLoginUrl());
String nonce = wwwAuthenticateSubject.getNonce();
String qop = wwwAuthenticateSubject.getQop();
String nc = DigestUtil.getNc();
String cnonce = DigestUtil.getCnonce();
String opaque = wwwAuthenticateSubject.getOpaque();
String ha1 = DigestMD5Util.encode(username, realm, password);
String ha2 = DigestUtil.getHa2ByQop(qop, method, digestUri);
String response = DigestUtil.getResponseByQop(ha1, nonce, nc, cnonce, qop, ha2);
DigestAuthorizationSubject authorizationSubject = new DigestAuthorizationSubject()
.setCnonce(cnonce)
.setQop(qop)
.setNc(nc)
.setNonce(nonce)
.setRealm(realm)
.setUsername(username)
.setUri(digestUri)
.setResponse(response)
.setAlgorithm(wwwAuthenticateSubject.getAlgorithm())
.setOpaque(opaque);
String authStr = SubjectSerializeUtil.serialize(authorizationSubject);
// send authorization request
config.getCustomHeaders().put("Authorization","Digest "+authStr);
result = HttpAuthUtil.sendRequest(config.getLoginUrl(),
config.getHttpMethod(),
config.getCustomHeaders(),
config.getCustomParams(),
SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody()));
return result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private HttpAuthResponse doBasicAuth(JapUser japUser, HttpApiConfig config, HttpServletResponse servletResponse) {
/*
* see: The 'Basic' HTTP Authentication Scheme:https://datatracker.ietf.org/doc/html/rfc7617
*/
String basicAuth = "Basic " + Base64Utils.encode(japUser.getUsername() + ":" + japUser.getPassword());
config.getCustomHeaders().put("authorization",basicAuth);
HttpAuthResponse httpAuthResponse = HttpAuthUtil.sendRequest(config.getLoginUrl(),
config.getHttpMethod(),
config.getCustomHeaders(),
config.getCustomParams(),
SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody()));
return httpAuthResponse;
}
private HttpAuthResponse sendBearerTokenAuthRequest(String token, HttpApiConfig config){
String bearerToken = "Bearer " + token;
config.getCustomHeaders().put("Authorization",bearerToken);
HttpAuthResponse result = HttpAuthUtil.sendRequest(config.getLoginUrl(),
config.getHttpMethod(),
config.getCustomHeaders(),
config.getCustomParams(),
SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody()));
return result;
}
private HttpAuthResponse doBearerAuth(JapUser japUser, HttpApiConfig config, HttpServletResponse servletResponse) {
JapUser japUserInDb = japUserService.getByName(japUser.getUsername());
String token = null;
if (japUserInDb==null||japUserInDb.getToken() == null) {
token = doPreAuthForBearerToken(japUser, config, servletResponse);
}else{
token = japUserInDb.getToken();
}
HttpAuthResponse result = sendBearerTokenAuthRequest(token, config);
// old token expired, request for new token
if(result==null||!result.isSuccess()){
String newToken = doPreAuthForBearerToken(japUser, config, servletResponse);
result = sendBearerTokenAuthRequest(newToken, config);
}
return result;
}
/**
* do a pre-auth in Bearer auth to get token for this user
*
* @param japUser
* @param config
* @param servletResponse
*/
private String doPreAuthForBearerToken(JapUser japUser, HttpApiConfig config, HttpServletResponse servletResponse) {
HttpAuthResponse response = null;
// clean old auth token
config.getCustomHeaders().remove("Authorization");
config.getCustomHeaders().remove("authorization");
switch (config.getForBearerTokenEnum()) {
case by_basic:
response = this.doBasicAuth(japUser, new HttpApiConfig(config,config.getBearerTokenIssueUrl()), servletResponse);
break;
case by_digest:
response = this.doDigestAuth(japUser, new HttpApiConfig(config,config.getBearerTokenIssueUrl()), servletResponse);
break;
case by_header:
config.getCustomHeaders().put("username",japUser.getUsername());
config.getCustomHeaders().put("password",japUser.getPassword());
response = HttpAuthUtil.sendRequest(config.getLoginUrl(),
config.getHttpMethod(),
config.getCustomHeaders(),
config.getCustomParams(),
SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody()));
break;
case by_params:
config.getCustomParams().put("username",japUser.getUsername());
config.getCustomParams().put("password",japUser.getPassword());
response = HttpAuthUtil.sendRequest(config.getLoginUrl(),
config.getHttpMethod(),
config.getCustomHeaders(),
config.getCustomParams(),
SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody()));
break;
case by_body:
String body = SimpleAuthJsonUtil.getJsonStrByJapUserAndParams(japUser,config.getCustomBody());
response = HttpAuthUtil.sendRequest(config.getLoginUrl(),
config.getHttpMethod(),
config.getCustomHeaders(),
config.getCustomParams(),
body);
break;
default:
break;
}
// pre-auth field
if (response==null||!response.isSuccess()) {
return null;
}
// get token from response body
String token = config.getGetTokenFromResponseStrategy().getToken(response.getBody());
if (token == null || token.length() == 0) {
return null;
}
// save jap user to db
japUser.setToken(token);
japUserService.createAndGetHttpApiUser(japUser);
return token;
}
/**
* get user information from request according to http api config.
*
* @param request
* @param config
* @return jap user contains username and password
*/
public JapUser getJapUser(HttpServletRequest request, HttpApiConfig config) {
JapUser japUser = null;
String username = null;
String password = null;
switch (config.getAuthInfoField()) {
case params:
username = request.getParameter("username");
password = request.getParameter("password");
japUser = new JapUser().setUsername(username).setPassword(password);
break;
case header:
username = request.getHeader("username");
password = request.getHeader("password");
japUser = new JapUser().setUsername(username).setPassword(password);
break;
case body:
try {
// get auth info body from request body.
japUser = config.getRequestBodyToJapUserStrategy().decode(request);
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
break;
}
if (null == japUser || null == japUser.getUsername()) {
return null;
}
return japUser;
}
/**
* check http api config.
* NOTICE:
* 1. When use "GET" as request method, custom body is not supported.
* 2. Can not custom body and params at the same time.
* @param sourceConfig The parameters passed in by the caller
* @param targetConfigClazz The actual parameter class type
* @throws JapException
*/
@Override
protected void checkAuthenticateConfig(AuthenticateConfig sourceConfig, Class<?> targetConfigClazz) throws JapException {
super.checkAuthenticateConfig(sourceConfig, targetConfigClazz);
HttpApiConfig config = (HttpApiConfig) sourceConfig;
if (config == null ||
config.getAuthSchema() == null ||
config.getHttpMethod() == null ||
config.getLoginUrl() == null ||
config.getAuthInfoField() == null ||
(config.getHttpMethod()==HttpMethodEnum.get&&(config.getCustomBody()!=null&&config.getCustomBody().size()!=0))||
((config.getCustomBody()!=null&&config.getCustomBody().size()!=0)&&(config.getCustomParams()!=null&&config.getCustomParams().size()!=0))
){
throw new JapException(JapErrorCode.ERROR_HTTP_API_CONFIG);
}
}
}
package com.fujieid.jap.httpapi.enums;
/**
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public enum AuthInfoFieldEnum {
header,
params,
body
}
package com.fujieid.jap.httpapi.enums;
/**
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public enum AuthSchemaEnum {
basic,
digest,
bearer
}
package com.fujieid.jap.httpapi.enums;
/**
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public enum ForBearerTokenEnum {
by_header,
by_params,
by_body,
by_basic,
by_digest
}
package com.fujieid.jap.httpapi.enums;
/**
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public enum HttpMethodEnum {
post,
get
}
package com.fujieid.jap.httpapi.strategy;
/**
* Define how to get token from the third-party server's response when request to issue a bearer token
*
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
@FunctionalInterface
public interface GetTokenFromResponseStrategy {
/**
* get token from body
* @param body response body
* @return token
*/
String getToken(String body);
}
package com.fujieid.jap.httpapi.strategy;
import com.fujieid.jap.core.JapUser;
import javax.servlet.http.HttpServletRequest;
/**
* Implement this functional interface if your auth info in the request body is not standard json format.
*
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
@FunctionalInterface
public interface RequestBodyToJapUserStrategy {
/**
* get a JapUser from request
* @param request request
* @return JapUser
* @throws Exception
*/
JapUser decode(HttpServletRequest request) throws Exception;
}
package com.fujieid.jap.httpapi.subject;
/**
* The Authorization Request Header
* @see <a href="https://en.wikipedia.org/wiki/Digest_access_authentication" target="_blank">https://en.wikipedia.org/wiki/Digest_access_authentication</a>
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.2" target="_blank">https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.2</a>
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class DigestAuthorizationSubject{
private String username;
private String realm;
private String nonce;
private String uri;
private String response;
private String qop;
private String nc;
private String cnonce;
private String pop;
private String opaque;
private String digest;
private String algorithm;
public String getQop() {
return qop;
}
public DigestAuthorizationSubject setQop(String qop) {
this.qop = qop;
return this;
}
public String getUsername() {
return username;
}
public DigestAuthorizationSubject setUsername(String username) {
this.username = username;
return this;
}
public String getRealm() {
return realm;
}
public DigestAuthorizationSubject setRealm(String realm) {
this.realm = realm;
return this;
}
public String getNonce() {
return nonce;
}
public DigestAuthorizationSubject setNonce(String nonce) {
this.nonce = nonce;
return this;
}
public String getUri() {
return uri;
}
public DigestAuthorizationSubject setUri(String uri) {
this.uri = uri;
return this;
}
public String getPop() {
return pop;
}
public DigestAuthorizationSubject setPop(String pop) {
this.pop = pop;
return this;
}
public String getNc() {
return nc;
}
public DigestAuthorizationSubject setNc(String nc) {
this.nc = nc;
return this;
}
public String getCnonce() {
return cnonce;
}
public DigestAuthorizationSubject setCnonce(String cnonce) {
this.cnonce = cnonce;
return this;
}
public String getResponse() {
return response;
}
public DigestAuthorizationSubject setResponse(String response) {
this.response = response;
return this;
}
public String getOpaque() {
return opaque;
}
public DigestAuthorizationSubject setOpaque(String opaque) {
this.opaque = opaque;
return this;
}
public String getDigest() {
return digest;
}
public DigestAuthorizationSubject setDigest(String digest) {
this.digest = digest;
return this;
}
public String getAlgorithm() {
return algorithm;
}
public DigestAuthorizationSubject setAlgorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
}
package com.fujieid.jap.httpapi.subject;
/**
* The WWW-Authenticate Response Header
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.1" target="_blank">https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.1</a>
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class DigestWwwAuthenticateSubject {
private String realm;
private String domain;
private String nonce;
private String opaque;
private String algorithm;
private String stale;
private String qop;
public String getQop() {
return qop;
}
public DigestWwwAuthenticateSubject setQop(String qop) {
this.qop = qop;
return this;
}
public DigestWwwAuthenticateSubject() {
}
public String getRealm() {
return realm;
}
public DigestWwwAuthenticateSubject setRealm(String realm) {
this.realm = realm;
return this;
}
public String getDomain() {
return domain;
}
public DigestWwwAuthenticateSubject setDomain(String domain) {
this.domain = domain;
return this;
}
public String getNonce() {
return nonce;
}
public DigestWwwAuthenticateSubject setNonce(String nonce) {
this.nonce = nonce;
return this;
}
public String getOpaque() {
return opaque;
}
public DigestWwwAuthenticateSubject setOpaque(String opaque) {
this.opaque = opaque;
return this;
}
public String getAlgorithm() {
return algorithm;
}
public DigestWwwAuthenticateSubject setAlgorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public String getStale() {
return stale;
}
public DigestWwwAuthenticateSubject setStale(String stale) {
this.stale = stale;
return this;
}
}
package com.fujieid.jap.httpapi.subject;
import java.util.List;
import java.util.Map;
/**
* packaging auth result
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class HttpAuthResponse {
private boolean success;
private String body;
private Map<String, List<String>> headers;
public HttpAuthResponse(boolean success, String body, Map<String, List<String>> headers) {
this.success = success;
this.body = body;
this.headers = headers;
}
public boolean isSuccess() {
return success;
}
public HttpAuthResponse setSuccess(boolean success) {
this.success = success;
return this;
}
public String getBody() {
return body;
}
public HttpAuthResponse setBody(String body) {
this.body = body;
return this;
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public HttpAuthResponse setHeaders(Map<String, List<String>> headers) {
this.headers = headers;
return this;
}
}
package com.fujieid.jap.httpapi.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Splice strings by ":", and encode using md5. eg:"MD5(MD5(username:realm:password):nonce:cnonce)"
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public final class DigestMD5Util {
private final static String[] HEX_ARRAY = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
public static String encode(String... strs){
StringBuilder stringBuffer = new StringBuilder();
boolean isFirst = true;
for(String str:strs){
if(isFirst){
stringBuffer.append(str);
isFirst=false;
}else{
stringBuffer.append(":").append(str);
}
}
// Create an information summary with MD5 algorithm
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] bytes = md.digest(stringBuffer.toString().getBytes());
return byteArrayToHex(bytes);
}
private static String byteArrayToHex(byte[] b){
StringBuffer sb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
sb.append(byteToHex(b[i]));
}
return sb.toString();
}
public static String byteToHex(byte b) {
int n = b;
if (n < 0){
n = n + 256;
}
int d1 = n / 16;
int d2 = n % 16;
return HEX_ARRAY[d1]+ HEX_ARRAY[d2];
}
}
package com.fujieid.jap.httpapi.util;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
/**
* refer: https://github.com/meyfa/java-sha256/blob/master/src/main/java/net/meyfa/sha256/Sha256.java
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class DigestSha256Util {
private static final int[] K = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
private static final int[] H0 = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
private static final int BLOCK_BITS = 512;
private static final int BLOCK_BYTES = BLOCK_BITS / 8;
// working arrays
private static final int[] W = new int[64];
private static final int[] H = new int[8];
private static final int[] TEMP = new int[8];
/**
* Hashes the given message with SHA-256 and returns the hash.
*
* @param message The bytes to hash.
* @return The hash's bytes.
*/
public static byte[] hash(byte[] message) {
// let H = H0
System.arraycopy(H0, 0, H, 0, H0.length);
// initialize all words
int[] words = pad(message);
// enumerate all blocks (each containing 16 words)
for (int i = 0, n = words.length / 16; i < n; ++i) {
// initialize W from the block's words
System.arraycopy(words, i * 16, W, 0, 16);
for (int t = 16; t < W.length; ++t) {
W[t] = smallSig1(W[t - 2]) + W[t - 7] + smallSig0(W[t - 15]) + W[t - 16];
}
// let TEMP = H
System.arraycopy(H, 0, TEMP, 0, H.length);
// operate on TEMP
for (int t = 0; t < W.length; ++t) {
int t1 = TEMP[7] + bigSig1(TEMP[4]) + ch(TEMP[4], TEMP[5], TEMP[6]) + K[t] + W[t];
int t2 = bigSig0(TEMP[0]) + maj(TEMP[0], TEMP[1], TEMP[2]);
System.arraycopy(TEMP, 0, TEMP, 1, TEMP.length - 1);
TEMP[4] += t1;
TEMP[0] = t1 + t2;
}
// add values in TEMP to values in H
for (int t = 0; t < H.length; ++t) {
H[t] += TEMP[t];
}
}
return toByteArray(H);
}
/**
* <b>Internal method, no need to call.</b> Pads the given message to have a length
* that is a multiple of 512 bits (64 bytes), including the addition of a
* 1-bit, k 0-bits, and the message length as a 64-bit integer.
* The result is a 32-bit integer array with big-endian byte representation.
*
* @param message The message to pad.
* @return A new array with the padded message bytes.
*/
public static int[] pad(byte[] message) {
// new message length: original + 1-bit and padding + 8-byte length
// --> block count: whole blocks + (padding + length rounded up)
int finalBlockLength = message.length % BLOCK_BYTES;
int blockCount = message.length / BLOCK_BYTES + (finalBlockLength + 1 + 8 > BLOCK_BYTES ? 2 : 1);
final IntBuffer result = IntBuffer.allocate(blockCount * (BLOCK_BYTES / Integer.BYTES));
// copy as much of the message as possible
ByteBuffer buf = ByteBuffer.wrap(message);
for (int i = 0, n = message.length / Integer.BYTES; i < n; ++i) {
result.put(buf.getInt());
}
// copy the remaining bytes (less than 4) and append 1 bit (rest is zero)
ByteBuffer remainder = ByteBuffer.allocate(4);
remainder.put(buf).put((byte) 0b10000000).rewind();
result.put(remainder.getInt());
// ignore however many pad bytes (implicitly calculated in the beginning)
result.position(result.capacity() - 2);
// place original message length as 64-bit integer at the end
long msgLength = message.length * 8L;
result.put((int) (msgLength >>> 32));
result.put((int) msgLength);
return result.array();
}
/**
* Converts the given int array into a byte array via big-endian conversion
* (1 int becomes 4 bytes).
*
* @param ints The source array.
* @return The converted array.
*/
private static byte[] toByteArray(int[] ints) {
ByteBuffer buf = ByteBuffer.allocate(ints.length * Integer.BYTES);
for (int i : ints) {
buf.putInt(i);
}
return buf.array();
}
private static int ch(int x, int y, int z) {
return (x & y) | ((~x) & z);
}
private static int maj(int x, int y, int z) {
return (x & y) | (x & z) | (y & z);
}
private static int bigSig0(int x) {
return Integer.rotateRight(x, 2)
^ Integer.rotateRight(x, 13)
^ Integer.rotateRight(x, 22);
}
private static int bigSig1(int x) {
return Integer.rotateRight(x, 6)
^ Integer.rotateRight(x, 11)
^ Integer.rotateRight(x, 25);
}
private static int smallSig0(int x) {
return Integer.rotateRight(x, 7)
^ Integer.rotateRight(x, 18)
^ (x >>> 3);
}
private static int smallSig1(int x) {
return Integer.rotateRight(x, 17)
^ Integer.rotateRight(x, 19)
^ (x >>> 10);
}
}
package com.fujieid.jap.httpapi.util;
import cn.hutool.core.lang.UUID;
import java.util.concurrent.atomic.LongAdder;
/**
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public final class DigestUtil {
private static final LongAdder LONG_ADDER = new LongAdder();
public static String getCnonce(){
return UUID.randomUUID().toString().replaceAll("-","").substring(0,16);
}
public static String getNc(){
LONG_ADDER.increment();
return LONG_ADDER.toString();
}
public static String getResponseByQop(String ha1, String nonce, String nc, String cnonce, String qop, String ha2) {
if(qop!=null&&(qop.contains("auth")||qop.contains("auth-init"))){
return DigestMD5Util.encode(ha1,nonce,nc,cnonce,qop,ha2);
}else{
return DigestMD5Util.encode(ha1,nonce,ha2);
}
}
public static String getHa2ByQop(String qop, String method, String digestUri) {
if(qop==null||!qop.contains("auth-init")){
return DigestMD5Util.encode(method,digestUri);
}else{
// TODO Support "auth-init" ha2 compute method
return null;
}
}
}
package com.fujieid.jap.httpapi.util;
import com.fujieid.jap.httpapi.enums.HttpMethodEnum;
import com.fujieid.jap.httpapi.subject.HttpAuthResponse;
import com.xkcoding.http.HttpUtil;
import com.xkcoding.http.support.SimpleHttpResponse;
import com.xkcoding.http.support.HttpHeader;
import com.xkcoding.http.support.SimpleHttpResponse;
import java.util.List;
import java.util.Map;
/**
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public final class HttpAuthUtil{
public static HttpAuthResponse sendRequest(String url,
HttpMethodEnum method,
Map<String,String> header,
Map<String,String> params,
String body){
SimpleHttpResponse response = null;
switch (method){
case get:
response = HttpUtil.get(url, params, new HttpHeader(header), false);
break;
case post:
if(body==null||"".equals(body)){
response = HttpUtil.post(url,params,new HttpHeader(header),false);
}else{
response = HttpUtil.post(url,body,new HttpHeader(header));
}
break;
default:break;
}
if(response==null){
return null;
}
String responseBody = response.getBody();
Map<String, List<String>> headers = response.getHeaders();
return new HttpAuthResponse(response.isSuccess(),responseBody,headers);
}
}
package com.fujieid.jap.httpapi.util;
import com.fujieid.jap.core.JapUser;
import java.util.Map;
/**
* Convert between json and auth info
*
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public final class SimpleAuthJsonUtil {
public static String getJsonStrByParams(Map<String,String> params){
return getJsonStrByJapUserAndParams(null,params);
}
public static String getJsonStrByJapUserAndParams(JapUser japUser, Map<String,String> params){
if(japUser==null&&params==null){
return null;
}
StringBuilder str = new StringBuilder();
str.append("{");
if(japUser!=null){
str.append("\"username\":\"").append(japUser.getUsername()).append("\",\"password:\"").append(japUser.getPassword()).append("\",");
}
if(params!=null){
params.forEach((key, value) -> str.append("\"").append(key).append("\":\"").append(value).append("\","));
}
if(str.charAt(str.length()-1)==','){
str.deleteCharAt(str.length()-1);
}
str.append("}");
return str.toString();
}
}
package com.fujieid.jap.httpapi.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Objects;
/**
* used to convert "Authorization Request Header" and "WWW-Authenticate Response Header" between string and object
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public final class SubjectSerializeUtil {
/**
* Deserialize string to object
* @param clazz
* @param str
* @return
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
public static Object deSerialize(Class<?> clazz,String str) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
str = str.replace("\\","").replace("\"","").trim();
String[] split = str.split(",");
HashMap<String, String> params = new HashMap<>(8);
for (String s : split) {
String[] param = s.split("=");
String paramKey = param[0].trim();
String paramValue = param[1].trim();
params.put(paramKey,paramValue);
}
Field[] fields = clazz.getDeclaredFields();
Object t = clazz.getDeclaredConstructor().newInstance();
for (Field field : fields) {
field.setAccessible(true);
String name = field.getName();
field.set(t,params.get(name));
}
return t;
}
/**
* Serialize object to string
* @param t
* @return
* @throws IllegalAccessException
*/
public static String serialize(Object t) throws IllegalAccessException {
Class<?> clazz = t.getClass();
StringBuilder sb = new StringBuilder();
Field[] fields = clazz.getDeclaredFields();
boolean firstParam = true;
for (Field field : fields) {
field.setAccessible(true);
String paramName = field.getName();
String paramVal = ((String) field.get(t));
if(Objects.isNull(paramVal)){
continue;
}
if(!firstParam){
sb.append(",");
}
firstParam = false;
sb.append(paramName).append("=\"").append(paramVal).append("\"");
}
return sb.toString();
}
}
package com.fujieid.jap.httpapi.util;
/**
* URLUtil
*
* @author zhihai.yu (mvbbb(a)foxmail.com)
* @version 1.0.0
* @since 1.0.0
*/
public class URLUtil {
private static final String HTTPS_PREFIX = "https://";
private static final String HTTP_PREFIX = "http://";
/**
* Get relative uri from uri.
* https://www.google.com ==>> www.google.com
* @param uri
* @return
*/
public static String getRelativeUri(String uri){
if(uri==null){
return null;
}
String relativeUri = null;
if(uri.startsWith(HTTPS_PREFIX)){
relativeUri = uri.replaceFirst(HTTPS_PREFIX,"");
}else if(uri.startsWith(HTTP_PREFIX)){
relativeUri = uri.replaceFirst(HTTP_PREFIX,"");
}else{
relativeUri = uri;
}
relativeUri = relativeUri.substring(relativeUri.indexOf('/'));
return relativeUri;
}
}
......@@ -48,11 +48,12 @@
<module>jap-mfa</module>
<module>jap-ids</module>
<module>jap-bom</module>
<module>jap-http-api</module>
</modules>
<properties>
<!-- jap version -->
<revision>1.0.5</revision>
<revision>1.0.2</revision>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- maven -->
......@@ -72,13 +73,13 @@
<mockito.version>2.23.4</mockito.version>
<hutool.version>5.5.7</hutool.version>
<jakarta.servlet.version>4.0.4</jakarta.servlet.version>
<justauth.version>1.16.2</justauth.version>
<justauth.version>1.16.1</justauth.version>
<jose4j.version>0.7.6</jose4j.version>
<slf4j-api.version>1.7.30</slf4j-api.version>
<jedis.version>3.2.0</jedis.version>
<kisso.version>3.7.6</kisso.version>
<simple-json.version>0.0.2</simple-json.version>
<simple-http.version>1.0.3</simple-http.version>
<simple-http.version>1.0.4</simple-http.version>
<zxing.version>3.3.3</zxing.version>
<googleauth.version>1.4.0</googleauth.version>
<commons-cli.version>1.4</commons-cli.version>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册