Hello everyone, I am Orange chang, yesterday shared “token mechanism”, you can review the core of OAuth 2.0, what token mechanism is, how to use, open source components, etc.
Today, we start to write the code on the ground. Based on the authorization of The agricultural Bank of China, we will first share the “Hand-hold access to OAuth 2.0 authorization service” with you as a third-party service.
I. Business background
Recently, the team helped the bank to conduct an interactive marketing activity. At the entrance of the activity, users participated in the activity by clicking on the banner page of the activity on the App of the relevant party.
Before entering an activity, the business side naturally needs to know who is participating in the activity and how to construct a login state for it.
This is why the orange side needs to access the OAuth 2.0 component of the row side. In essence, it is to obtain customer information and return to the activity business to form a login state, so as to participate in activities.
This access method uses the most complete licensing mechanism in OAuth 2.0, server-side initiated authorization, which is mostly hard-coded for ease of presentation.
What does third-party software need to do
1. Static registration
Also known as registration (registration information), it is required to apply for OAuth 2.0 access to the client in the management mode of the bank’s open platform, for the bank to ensure that the third party software is trusted.
Required information: IP address of the third-party application server, callback notification address of the third-party application, and application permission.
String ip = "xxx.xx.xx.xx";
String callBackUrl = "https://xxx.xxx.xx/oauth/login/success";
Copy the code
After being processed by the background personnel of the authorization service, relevant configurations are issued to uniquely identify third-party software.
Here is a simple example template:
String appid = "appid_001";
String appSecret = "appSecret_001";
String scope = "user_info";
Copy the code
2. Guide user authorization
1) Step 1: The user accesses the third-party software and judges the credentials
If the JWT is not carried or has expired, the server responds to the Http status code 401 to the front end, which requests the server to initiate authorization interface.
/ / say visits curl https://xxx.xx.xx/api/activity/act-01/index; homepage interface // Server response 401 {"message": "user session expired, please log in again!" , "statusCode": 401 }Copy the code
2) Step 2: The client receives 401 and requests the authorization interface
The third-party software interacts with the authorization service to obtain the authorization page address and redirect the user to the authorization page.
- The controller layer
@Slf4j @RequestMapping("/oauth") @RestController public class OAuthController { @GetMapping("/login") public RedirectView login(final String redirect) { String sceneId = IdUtil.simpleUUID(); JSONObject sceneInfo = JSONUtil.createObj().putOnce(OAuthConstant.REDIRECT_FOR_FRONT, redirect); Set (oauthconstant. KEY_PRE_OAUTH_SCENE + sceneId, sceneInfo, 300); String oauthUrl = oAuthService.buildOauthUrl(sceneId, callbackUrl); Log.info ("[initiate XXX line OAuth authorization] build OAuth url: [{}]", oauthUrl); // redirect return new RedirectView(oauthUrl); }}Copy the code
Log printing:
The authorization address constructed by XXXX [initiate authorization] is: [http://xxx/oauth/authorize?client_id=xxx&redirect_uri=http%3A%2F%2Fxxx%2F%2Foauth%2Flogin%2Fsuccess&state=d8cb3943cd3a4 5818711fa4f6a8820e9&scope=custid%2Cphone&response_type=code]Copy the code
- The service implementation
@ Override public String buildOauthUrl (final String sceneId) {/ / callback address String callbackUrl = applicationConfig. GetAppUrl () + "/oauth/login/success"; // String notifyUrl = urlBuilder.of (callbackUrl, charsetutil.charset_UTF_8).addQuery("sceneId", sceneId).build(); // String notifyUrl = urlBuilder.of (callbackUrl, charsetutil.charset_utF_8). return xxxOAuthService.buildOauthUrl(notifyUrl); }Copy the code
According to the requirements of standard OAuth 2.0, it is more reasonable for the service layer to request authorization service acquisition. The design of authorization service here is a little unreasonable, which will be adjusted later.
3) Step 3: Authorization service callback notification, distribution of temporary authorization code
@RequestMapping("/login/success") public RedirectView loginSuccess(final String sceneId, Final String code) {log.info("[XXX Line authorization callback notification] sceneId: [{}], code: [{}]", sceneId, code); // Subsequent business operations}Copy the code
Log printing:
XXXX [oauth service]- Callback, received data: code: [XXX], state: [XXX]Copy the code
4) Step 4: Third-party services exchange tokens through code
private String getToken(final String clientId, final String clientSecret, final String code) { JSONObject tokenJson = this.getTokenFromOAuthServer(clientId, clientSecret, code, redirectUri); String accessToken = tokenJson.getStr("access_token"); if (! JSONUtil.isNull(tokenJson) && StrUtil.isNotEmpty(accessToken)) { return accessToken; } throw new RuntimeException(" Token acquisition exception! ") ); } private JSONObject getTokenFromOAuthServer(Final String clientId, final String clientSecret, final String code, Final String redirectUri) {/ / request resources address String requertUrl = oauthServerConfig. GetBaseUrl () + "/ request/token". // Build request parameters Map<String, Object> formMap = new HashMap<>(5); Formmap. put("grant_type", "authorization_code"); formMap.put("client_id", clientId); formMap.put("client_secret", clientSecret); formMap.put("code", code); JSONObject Response = this.doPostFormData(requertUrl, formMap); Log.info ("[obtaining token from authorization service] result :[{}]", response); return response; } /** * remove the post request method, Form-data upload parameter * * @author huangyin */ private JSONObject doPostFormData(final String sourceUrl, final Map<String, Object> formArgs) {try {// Use open source hutool String response = HttpRequest.post(sourceUrl).form(formArgs).timeout(3000).execute().body(); Log.info ("[Post form request from Authorization service] Request address: [{}], request parameter: [{}], original response: [{}]", sourceUrl, formArgs, Response); JSONObject responseJson = jsonUtil.parseobj (response); if (jsonutil.isjson (response)) { String code = responseJson.getStr("code"); JSONObject data = responseJson.getJSONObject("data"); if ("0000".equals(code) && ! JSONUtil.isNull(data)) { return data; }}} catch (Exception e) {log.error("[{}], [{}], [{}], [{}], [{}]", sourceUrl, formArgs, e.getMessage()); } throw new RuntimeException(" Authorization service exception! ") ); }Copy the code
Print logs:
XXXX [Authorization service code to obtain token] Result :[{"access_token":" XXX ","expires_in":7200}]Copy the code
Step 5: Get the credentials and access the business interface
When the authorization code is exchanged for a credential, the credential is used to obtain the user’s data in the protected resource service, for example, to obtain user information.
public String getUserInfoFromOAuthServer(String token) { String sourceUrl = oauthServerConfig.getBaseUrl() + "/oauth/userInfo"; String Response = Httprequest.post (sourceUrl). Header ("Authorization", "Bearer " + accessToken) .timeout(3000).execute().body(); Log.info ("[get user information from authorization service POST request] Request address: [{}], original response: [{}]", sourceUrl, response); JSONObject responseJson = jsonUtil.parseobj (response); if (jsonutil.isjson (response)) { String rtCode = responseJson.getStr("code"); String data = responseJson.getStr("data"); if ("0000".equals(rtCode) && StrUtil.isNotEmpty(data)) { return data; }}} catch (Exception e) {log.error("[{}], Exception: [{}]", sourceUrl, LLDB message ());}}} Catch (Exception e) {log.error("[{}], Exception: [{}]", sourceUrl, LLDB message ()); } throw new RuntimeException(" Authorization service exception! ") ); }Copy the code
Print logs:
[{" openID ":" XXX ","headImg":" XXX "}]Copy the code
6) Step 6: Store user information and distribute business code to the front end
Get the user information and write it into the business table of the activity service. Then notify the front end that authorization is completed and issue the temporary code of the activity business to the client so that the client can exchange the JWT of the activity business.
- User information is stored in the database
public RedirectView loginSuccess(String ...) {// copy OauthUser OauthUser = beanutil.copyProperties (userInfoDto, oAuthUser.class); oauthUserService.createOrUpdate(oauthUser); Return this.redirectFrontEndUrl(state, userInfoDto); }Copy the code
- The server notifies the front end
private RedirectView redirectFrontEndUrl(final String sceneId, Final UserInfoDto UserInfoDto) {// generate businessCode String businessCode = idutil.simpleuuid (); Try {/ / pegging to the front-end notify address cache. The set (SmallBeanOauthConstant SMALL_KEY_PRE_USER_INFO + businessCode, userInfoDto, 300); JSONObject sceneInfo = JSONUtil.parseObj(cache.get(SmallBeanOauthConstant.SMALL_KEY_PRE_OAUTH_SCENE + sceneId)); String redirectFrontEndUrl = sceneInfo.getStr("redirectFrontEndUrl"); If (strutil. isEmpty(redirectFrontEndUrl)) {log.warn(" authorization: distribute business code to front address isEmpty!" ); // The TODO section needs to be set to a default value, or to respond to the front end 401, } String notifyUrl = urlBuilder.of (redirectFrontEndUrl, standardCharsets.utf_8).addQuery("code", businessCode) .build(); Return new RedirectView(notifyUrl); } catch (Exception e) {log.error("[OAuth issue code to client Exception] [{}], [{}]", sceneId, e.getMessage(), e.getCause()); } throw new RuntimeException(" Authorization failed, please try again later!" ); }Copy the code
7) Step 7: The client exchanges business JWT through code
/** * business code to JWT: @responseEntity */ @postMapping ("/login") public ResponseEntity<? > login(@RequestBody CodeToJwtDto codeToJwtDto) { ValidatorUtil.validateEntity(codeToJwtDto); String code = codeToJwtDto.getCode(); Object cacheUserInfo = cache.get(OAuthConstant.USER_INFO_PREFIX + code); If (null == cacheUserInfo) {throw new ParamException(" Code illegal!" , HttpStatus.UNPROCESSABLE_ENTITY.value()); } // delete code cache.delete(oauthconstant. USER_INFO_PREFIX + code); // a series of other business processes, etc., then generate JWT Map<String, Object> tokenMap = this.buildjwt (activityUser); return ResponseEntity.status(HttpStatus.CREATED).body(tokenMap); } private Map<String, Object> buildJwt(ActivityUser ActivityUser) { Object> claims = new HashMap<>(2); claims.put("authId", activityUser.getId()); claims.put("authRole", "user"); String token = JwtUtil.generateToken(claims, applicationConfig.getExpiration(), applicationConfig.getTokenSigningKey()); TokenMap = new HashMap<>(3); tokenMap.put("accessToken", token); tokenMap.put("tokenType", "Bearer"); tokenMap.put("expiresIn", applicationConfig.getExpiration()); return tokenMap; }Copy the code
JWT obtained:
{
accessToken=header.payload.signature,
tokenType=Bearer,
expiresIn=86400
}
Copy the code
Third, summary
Today orange is taking you step by step to write code to access OAuth 2.0 authorization services, you need to remember a few points:
1. Pay attention to the official document of the authorization service. The open platform access document is a very important credential.
2. The access authorization of third-party software should adopt server-side initiated authorization as far as possible and use authorization code licensing mechanism, because it is safer and more complete.
OAuth 2.0 access code is a test of basic skills and code style, which uses Redis cache, Hutool to initiate Http requests and so on.
The next article will give you the interpretation of “hand by hand to build OAuth 2.0 authorized services”, thank you for your attention, if you feel benefit, welcome to like, forward, comment, thank you for recognition!
If you have any questions, you can also add my friend (WX: mbandtr), welcome to communicate.