由于业务实现中涉及到接入第三方系统(app接入有赞商城等),所以涉及到第三方系统需要获取用户信息(用户手机号、姓名等),为了保证用户信息的安全和接入方式的统一,采用Oauth2四种模式之一的授权码模式。
实际应用中由于合作商户,所以需要直接返回code,不需要用户手动授权,即静默模式,所以需要扩展框架,使其支持自动授权
项目使用的是spring-security-oauth2-2.0.15
由于默认情况下,需要用户授权通过才能生成授权码。所以需简要对框架进行扩展
spring-security-oauth2
获取code的controller:RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
SessionStatus sessionStatus, Principal principal) {
// Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
// query off of the authorization request instead of referring back to the parameters map. The contents of the
// parameters map will be stored without change in the AuthorizationRequest object once it is created.
AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);
Set<String> responseTypes = authorizationRequest.getResponseTypes();
if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
}
if (authorizationRequest.getClientId() == null) {
throw new InvalidClientException("A client id must be provided");
}
try {
if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
throw new InsufficientAuthenticationException(
"User must be authenticated with Spring Security before authorization can be completed.");
}
ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());
// The resolved redirect URI is either the redirect_uri from the parameters or the one from
// clientDetails. Either way we need to store it on the AuthorizationRequest.
String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
if (!StringUtils.hasText(resolvedRedirect)) {
throw new RedirectMismatchException(
"A redirectUri must be either supplied or preconfigured in the ClientDetails");
}
authorizationRequest.setRedirectUri(resolvedRedirect);
// We intentionally only validate the parameters requested by the client (ignoring any data that may have
// been added to the request by the manager).
oauth2RequestValidator.validateScope(authorizationRequest, client);
// Some systems may allow for approval decisions to be remembered or approved by default. Check for
// such logic here, and set the approved flag on the authorization request accordingly.
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
(Authentication) principal);
// TODO: is this call necessary?
boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
authorizationRequest.setApproved(approved);
// Validation is all done, so we can check for auto approval...
if (authorizationRequest.isApproved()) {
if (responseTypes.contains("token")) {
return getImplicitGrantResponse(authorizationRequest);
}
if (responseTypes.contains("code")) {
return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
(Authentication) principal));
}
}
// Place auth request into the model so that it is stored in the session
// for approveOrDeny to use. That way we make sure that auth request comes from the session,
// so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
model.put("authorizationRequest", authorizationRequest);
return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
}
catch (RuntimeException e) {
sessionStatus.setComplete();
throw e;
}
}
52行到59行可知,当approved
为true的时候会直接返回code码,不会需要用户授权,所以问题变成了如何让扩展的userApprovalHandler
生效
userApprovalHandler
生效@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(authUserDetailService)
.authorizationCodeServices(new JdbcAuthorizationCodeServices(dataSource))
.reuseRefreshTokens(false)
.userApprovalHandler(new AuthApprovalHandler())
.exceptionTranslator(customWebResponseExceptionTranslator)
;
}
9行:AuthotizationServer
中增加配置自定义配置
需要APP端定制WebView开发,根据/oauth/authrorize
路径参数中增加token
请求路径:
参数说明:
参数名称 | 类型 | 是否必填 | 描述 |
---|---|---|---|
response_type | String | 是 | 固定值“code” |
client_id | String | 是 | 第三方配置的client_id |
redirect_uri | String | 是 | 第三方配置的回调地址 |
state | String | 否 | 第三方自定义使用 |
请求示例:
curl -X POST http://localhost:8421/oauth/authorize -H 'Authorization: Bearer b7c2d63f-edff-4790-add9-0b69df7321b5' -d 'response_type=code&client_id=external&redirect_uri=http://www.baidu.com&state=123'
返回结果:重定向redirect_uri
路径
请求参数:
参数名称 | 类型 | 是否必填 | 描述 |
---|---|---|---|
client_id | String | 是 | 第三方配置的client_id |
client_secret | String | 是 | 第三方配置的密钥 |
code | String | 是 | 申请的code |
grant_type | String | 是 | 固定值“authorization_code” |
redirect_uri | String | 是 | 第三方配置的回调地址,必须与生成code时的uri一样 |
请求示例:
curl -X POST http://localhost:8421/oauth/token -d 'grant_type=authorization_code&client_id=external&client_secret=D524C1A0811DA49592F841085CC0063EB62B3001252A94542795D1CA9824A941&redirect_uri=http://www.baidu.com&code=4TCYkV'
返回结果:
{"access_token":"95b5be18-49a3-44e1-a527-d5da036cfc3f","token_type":"bearer","refresh_token":"b4488c7d-1e8c-4317-a955-1f4bda013a35","expires_in":9891370,"scope":"auth_base"}
暂时不支持
根据获取到的授权token访问用户资源信息
请求示例:
curl -X POST https://wuxi.test.brightcns.cn/api/v2/user/external/info -H 'Authorization: Bearer e86d752e-8d72-4a33-aa98-8e158ac5b50b'
返回结果:
{"success":true,"msg":"success","code":"SUCCESS","data":{"userId":4738295200051366773,"phone":13916413714,"nickname":"13916413714","avatar":"http://wxcardoss.oss-cn-shanghai.aliyuncs.com/null","realName":null,"extendParam":null}}
本文转载自博客园作者**浮生半日**的Spring Security OAuth2 授权码模式一文。