SpringMVC Custom ArgumentResolver
皆さんが微信の開発をするには、微信の授権という問題にかかわっていると思います.では、プロジェクトで許可が必要なURLについては、どのように設計開発されているのでしょうか.皆さんは普通2つの案があると思います.一つはサーブレットの中のFilterで、もう一つはSpring MVCの中のHandlerInterceptorです.微信のユーザ情報を取得する必要がある場合、Cookieに関連するcodeを追加し、ブロッキングメカニズムを使用して対面URLに権限を付与することができます.
1、プランを考える
まず、この2つの案を分析してみましょう. Filter:Filterの後ろのFilterを検証するにはドメイン名の判断が必要です.また、Controllerのメソッドに値を設定することはできません. HandlerInterceptor:パラメータを取得でき、メソッドに値を設定できません.
では、この問題を解決する方法はありますか?答えはある.Spring MVCのカスタムメソッドパラメータを使用して解析し、WeChatライセンスで返されたユーザー情報をHandleMethod、すなわち@RequstMappingを定義するControllerに交換する方法を使用できます.これにより、特殊なURLに対して、特殊なパラメータで権限を付与できるかどうかを区別することができます.メソッドに認証結果がパラメータとして渡されることをマークできます.
2、HandlerMethodArgumentResolver
3、コード構想
このビジネスシーンで使用されるURL.(少なくとも1回、最大3回)フロー1:COOKIEにはcodeがあり、正しいユーザ情報フロー2:COOKIEにもcodeがあるが、正しいユーザ情報が得られなかった(1)、ホップ微信授権(2)授権が戻ってきた(私たちのURLを呼び出して戻ってリダイレクト)、CODEは正しいユーザー情報を取得しました(3)-対応するシーン(以前は許可されていませんでしたが、現在は許可されています.または、以前は許可が期限切れで、以前はサイレント許可だったが、現在は確認許可になりました).
この2つの異なるプロセスは、クッキー内のcodeを考え、認証を行い、HandleMethodにユーザー情報を交換して使用することができる.
4、コード実装
1)Spring MVCでのControllerでのメソッド注記を定義します.メソッドパラメータ解析に使用します.
2)ユーザ情報定義ユーザ情報は、HandlerMethodで使用することができる.
3)微信アドレス
4)カスタムHandlerMethodArgumentResolverはSpringのHandlerMethodArgumentResolverを実現する.微信の検証とユーザー情報の取得を行う.
5)Springに組み込まれた解析器管理
6)プロジェクト応用
これにより、WechatAuthInfo注釈を使用してURLの認証管理やユーザ情報の取得が可能になります.もちろん、このメソッドパラメータを使用しないで、マイクロライセンスのみを使用することができます.あなたの具体的なビジネスロジックに基づいて考えます.
5、まとめ
この方式を使うには主に以下の3つの考慮点があります. out of the box:開梱即用で、特定のURLに対して、選択的に使用できます. open:ControllerはFilterとHandlerInterceptorに比べてメソッド上位層(開発者)に近づくことができる. easy test.シミュレーションの許可、Controllerはデバッグすることができて、Controllerは1つの注釈のマークだけあって、高度にデバッグすることができて便利で高いです.コードの変更とコードの変更は同じ場所ではありません.権限が必要なときにこの注釈を追加し、必要でないときに注釈することができます.FilterやHandlerInterceptorより便利です.
1、プランを考える
まず、この2つの案を分析してみましょう.
では、この問題を解決する方法はありますか?答えはある.Spring MVCのカスタムメソッドパラメータを使用して解析し、WeChatライセンスで返されたユーザー情報をHandleMethod、すなわち@RequstMappingを定義するControllerに交換する方法を使用できます.これにより、特殊なURLに対して、特殊なパラメータで権限を付与できるかどうかを区別することができます.メソッドに認証結果がパラメータとして渡されることをマークできます.
2、HandlerMethodArgumentResolver
public interface HandlerMethodArgumentResolver {
/**
*
*/
boolean supportsParameter(MethodParameter parameter);
/**
*
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}
3、コード構想
このビジネスシーンで使用されるURL.(少なくとも1回、最大3回)フロー1:COOKIEにはcodeがあり、正しいユーザ情報フロー2:COOKIEにもcodeがあるが、正しいユーザ情報が得られなかった(1)、ホップ微信授権(2)授権が戻ってきた(私たちのURLを呼び出して戻ってリダイレクト)、CODEは正しいユーザー情報を取得しました(3)-対応するシーン(以前は許可されていませんでしたが、現在は許可されています.または、以前は許可が期限切れで、以前はサイレント許可だったが、現在は確認許可になりました).
この2つの異なるプロセスは、クッキー内のcodeを考え、認証を行い、HandleMethodにユーザー情報を交換して使用することができる.
4、コード実装
1)Spring MVCでのControllerでのメソッド注記を定義します.メソッドパラメータ解析に使用します.
/**
*
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WechatAuthInfo {
public enum AidFrom {
PATH_VARIABLE,
REQUEST_PARAM,
HOSTNAME
}
/**
* , true, ,
*/
boolean required() default true;
/**
* aid
*/
AidFrom aidFrom() default AidFrom.PATH_VARIABLE;
/**
* aid path, PathVariable RequestParam
*/
String name() default "aid";
}
2)ユーザ情報定義ユーザ情報は、HandlerMethodで使用することができる.
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown=true)
public class SilentAuthorizationResult {
@JsonProperty("AId")
private Long aid;
@JsonProperty("OpenId")
private String openId;
@JsonProperty("BizOpenId")
private String bizOpenId;
}
3)微信アドレス
@Builder
@Log
public class OAuthProvider {
private String silentAuthUrl;
private String silentResultUrl;
private String confirmAuthUrl;
private String confirmResultUrl;
public SilentAuthorizationResult silentAuth(String weimobSID) {
try {
String url = String.format(silentResultUrl, URLEncoder.encode(weimobSID, "utf-8"));
ObjectNode response = HTTPClientUtils.sendHTTPRequest(url, null, "GET");
SilentAuthorizationResult authInfo = JSON.parseObject(response.toString(), SilentAuthorizationResult.class);
if (authInfo == null || StringUtils.isEmpty(authInfo.getOpenId())) {
//
return null;
}
return authInfo;
} catch (Exception e) {
// 。。。。
log.info(e.getLocalizedMessage());
return null;
}
}
public ConfirmAuthorizationResult confirmAuth(String weimobSID) {
try {
String url = String.format(confirmResultUrl, URLEncoder.encode(weimobSID, "utf-8"));
ObjectNode response = HTTPClientUtils.sendHTTPRequest(url, null, "GET");
ConfirmAuthorizationResult authInfo = JSON.parseObject(response.toString(), ConfirmAuthorizationResult.class);
if (authInfo == null || StringUtils.isEmpty(authInfo.getOpenId()) || StringUtils.isEmpty(authInfo.getNickName())) {
//
return null;
}
return authInfo;
} catch (Exception e) {
// 。。。。
log.info(e.getLocalizedMessage());
return null;
}
}
public String silentUrl(Long aid, String url) {
try {
return String.format(silentAuthUrl, URLEncoder.encode(String.valueOf(aid), "utf-8"), URLEncoder.encode(String.valueOf(aid), "utf-8"), URLEncoder.encode(url, "utf-8"));
} catch (UnsupportedEncodingException ignored) {
//
}
return null;
}
public String confirmUrl(Long aid, String url) {
try {
return String.format(confirmAuthUrl, URLEncoder.encode(String.valueOf(aid), "utf-8"), URLEncoder.encode(String.valueOf(aid), "utf-8"), URLEncoder.encode(url, "utf-8"));
} catch (UnsupportedEncodingException ignored) {
//
}
return null;
}
}
4)カスタムHandlerMethodArgumentResolverはSpringのHandlerMethodArgumentResolverを実現する.微信の検証とユーザー情報の取得を行う.
@ControllerAdvice
public class AuthInfoMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final Pattern HOST_PATTERN = Pattern.compile("^(\\d+)\\..*$");
private OAuthProvider provider;
private String baseUrl;
public String getBaseUrl() {
return baseUrl;
}
/**
* controller contextPath
*
* @param baseUrl url
*/
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public void setProvider(OAuthProvider provider) {
this.provider = provider;
}
public OAuthProvider getProvider() {
return provider;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class> paramType = parameter.getParameterType();
return parameter.hasParameterAnnotation(WechatAuthInfo.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (provider == null) {
provider = OAuthProvider.builder().silentAuthUrl(Constant.WECHAT_SILENT_OAUTH_URL).confirmAuthUrl(Constant.WECHAT_CONFIRM_OAUTH_URL).silentResultUrl(Constant.WECHAT_SILENT_OAUTH_RESULT_URL).confirmResultUrl(Constant.WECHAT_CONFIRM_OAUTH_RESULT_URL).build();
}
WechatAuthInfo annotation = parameter.getParameterAnnotation(WechatAuthInfo.class);
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
boolean found = false;
Long aid = null;
String name = annotation.name();
boolean isConfirm = ConfirmAuthorizationResult.class.isAssignableFrom(parameter.getParameterType());
// aid
switch (annotation.aidFrom()) {
case PATH_VARIABLE:
Map variables = getUriTemplateVariables(webRequest);
String pathVariable = variables.get(name);
if (StringUtils.hasText(pathVariable)) {
aid = Long.valueOf(pathVariable);
}
break;
case REQUEST_PARAM:
String requestParam = request.getParameter(name);
if (StringUtils.hasText(requestParam)) {
aid = Long.valueOf(requestParam);
}
break;
case HOSTNAME:
String host = request.getHeader("Host");
if (StringUtils.hasText(host)) {
String hostValue = HOST_PATTERN.matcher(host).replaceAll("$1");
if (StringUtils.hasText(hostValue)) {
aid = Long.valueOf(hostValue);
}
}
break;
default:
break;
}
// aid
if (aid == null) {
if (annotation.required()) {
throw new MissingServletRequestParameterException(annotation.name(), Long.class.getName());
}
//
return null;
}
// url, url
String url = String.format(getBaseUrl(), aid) +
(request.getPathInfo() == null ? request.getServletPath() : request.getPathInfo());
StringBuilder currentUrl = new StringBuilder(url);
if (StringUtils.hasText(request.getQueryString())) {
currentUrl.append("?");
currentUrl.append(request.getQueryString());
}
// cookie
String weimobSID = null;
String cookieName = String.format("SessionId_%s", aid);
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (cookieName.equals(cookie.getName())) {
weimobSID = cookie.getValue();
if (StringUtils.hasText(weimobSID)) {
break;
}
}
}
}
// cookie
if (!StringUtils.hasText(weimobSID) && annotation.required()) {
throw new WechatAuthInfoMissingException(isConfirm ? provider.confirmUrl(aid, currentUrl.toString()) : provider.silentUrl(aid, currentUrl.toString()), "Auth into required. redirecting");
}
// cookie
SilentAuthorizationResult authInfo;
if (isConfirm) {
ConfirmAuthorizationResult confirmAuthInfo = provider.confirmAuth(weimobSID);
//
if ((confirmAuthInfo == null || StringUtils.isEmpty(confirmAuthInfo.getNickName())) && annotation.required()) {
throw new WechatAuthInfoMissingException(provider.confirmUrl(aid, currentUrl.toString()), "Auth not completed. redirecting");
}
authInfo = confirmAuthInfo;
} else {
authInfo = provider.silentAuth(weimobSID);
if ((authInfo == null || StringUtils.isEmpty(authInfo.getOpenId())) && annotation.required()) {
throw new WechatAuthInfoMissingException(provider.silentUrl(aid, currentUrl.toString()), "Not auth. redirecting");
}
}
return authInfo;
}
@SuppressWarnings("unchecked")
protected final Map getUriTemplateVariables(NativeWebRequest request) {
Map variables = (Map) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (variables != null ? variables : Collections.emptyMap());
}
public static class WechatAuthInfoMissingException extends ServletRequestBindingException {
private static final long serialVersionUID = 2756877094069648764L;
public WechatAuthInfoMissingException(String url, String msg) {
super(msg);
this.url = url;
}
public WechatAuthInfoMissingException(String msg, Throwable cause) {
super(msg);
if (cause != null) {
initCause(cause);
}
}
private String url;
public String getUrl() {
return url;
}
}
/**
* BaseController ,
* fallback
*
* @param ex
* @return
* @throws Throwable
*/
@ExceptionHandler(WechatAuthInfoMissingException.class)
public ResponseEntity onWechatAuthMissing(WechatAuthInfoMissingException ex) throws Throwable {
if (ex.getUrl() != null) {
HttpHeaders headers = new HttpHeaders();
headers.add("Location", ex.getUrl());
return new ResponseEntity(" ", headers, HttpStatus.FOUND);
}
throw ex.getCause();
}
}
5)Springに組み込まれた解析器管理
<mvc:annotation-driven validator="validator">
<mvc:argument-resolvers>
<bean class="com.weimob.common.web.param.AuthInfoMethodArgumentResolver">
<property name="baseUrl">
<util:constant static-field="com.weimob.o2o.common.Constant.O2OConstant.O2O_H5_ADDRESS"/>
property>
bean>
mvc:argument-resolvers>
mvc:annotation-driven>
6)プロジェクト応用
@RequestMapping("yoururl")
public ModelAndView get(@WechatAuthInfo(name = "merchantId") SilentAuthorizationResult auth ...) {
// do something
return null;
}
これにより、WechatAuthInfo注釈を使用してURLの認証管理やユーザ情報の取得が可能になります.もちろん、このメソッドパラメータを使用しないで、マイクロライセンスのみを使用することができます.あなたの具体的なビジネスロジックに基づいて考えます.
5、まとめ
この方式を使うには主に以下の3つの考慮点があります.