Spring-Security-Oauth2 learning notes: Custom token return format

Keywords: Java Lombok Programming

1 Preface

In the previous chapter, we built the authorization authentication server and successfully returned the token. The default format of oauth2 is as follows:

{
    "access_token": "86d17c41-4de9-470a-a637-0acb7237099d",
    "token_type": "bearer",
    "refresh_token": "fb255411-9535-47ad-9f7e-40b21fac42cf",
    "expires_in": 43199,
    "scope": "all"
}

However, there is a convention about API data format when the front and back end interact. Now I want to return token in the following format:

{
    "code": 0,
    "msg": "Login succeeded!",
    "data": {
        "access_token": "86d17c41-4de9-470a-a637-0acb7237099d",
        "token_type": "bearer",
        "refresh_token": "fb255411-9535-47ad-9f7e-40b21fac42cf",
        "expires_in": 43097,
        "scope": "all"
    }
}

I also refer to many other materials, such as https://blog.csdn.net/u013905744/article/details/100637224 , this article repackages the data returned by the / oauth/token interface in the way of aspect programming. I think this method is a bit cumbersome, so I haven't tried it. In addition https://segmentfault.com/a/1190000020317220?utm_source=tag-newest , the method used in this article is to rewrite the / oauth/token interface, directly call the postAccessToken method of TokenEndpoint to get the token and repackage it. I tried this method and it can be used, but there are some problems. When using this method, there are some problems in exception handling, not to mention the details.
In the process of checking the data, I also saw the question Posts sent by others. The requirements are the same as mine, but there is no specific code for the following answer, just a paragraph saying that the request can be forwarded to / oauth/token by the back end, and the result can be obtained before encapsulation. I think this method should be feasible, so I have the following specific implementation, now put on dry goods.

2. Custom token return format

The format agreed with the front end is as follows:

{
	"code":0,//Express success
	"msg":"",//information
	"data":{//data
	}
}

Therefore, we write a ResponseResult tool class to return data. The code is as follows:

package com.example.oauth2.util;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult {
    //Return status code
    private int code;

    //Return information
    private String msg;

    //Return data
    private Object data;

    public ResponseResult(Object data){
        this.code=0;
        this.msg="Operation succeeded!";
        this.data=data;
    }

    public ResponseResult(String msg,Object data){
        this.code=0;
        this.msg=msg;
        this.data=data;
    }

    public ResponseResult(int code,String msg){
        this.code=code;
        this.msg=msg;
        this.data=null;
    }
}

Here comes the core code. Specifically, write a / oauth/login. In this interface, use the RestTemplate request / oauth/token interface. After getting the token, use the ResponseResult encapsulation to return. The code is as follows:

package com.example.oauth2.controller;

import com.example.oauth2.util.ResponseResult;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
@RequestMapping("/oauth")
public class OauthController {
    @PostMapping("/login")
    public ResponseResult login(@RequestParam Map<String,Object> map){
        MultiValueMap<String,Object> paramsMap=new LinkedMultiValueMap<>();
        paramsMap.set("username",map.get("username"));
        paramsMap.set("password",map.get("password"));
        paramsMap.set("grant_type",map.get("grant_type"));
        RestTemplate restTemplate=new RestTemplate();
        restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(map.get("client_id").toString(),map.get("client_secret").toString()));
        OAuth2AccessToken token=restTemplate.postForObject("http://localhost:8080/oauth/token",paramsMap,OAuth2AccessToken.class);
        return new ResponseResult("Login succeeded!",token);
    }
}

Among them, restTemplate.getInterceptors() is very important. If this line of code is not added, a 401 error will be reported when using restTemplate. Now let's test it. Open postman, just like the test in the previous chapter, except that / oauth/token is changed to / oauth/login, and the result is as follows:

It can be seen that the return format is the format we require, but there is still a problem here. In the previous chapter, we also used refresh_token to refresh access_token, so we need to modify / oauth/login. First, we need to determine how grant_type requests the token, and then we need to make the request. The code is as follows:

	@PostMapping("/login")
    public ResponseResult login(@RequestParam Map<String,Object> map){
        MultiValueMap<String,Object> paramsMap=new LinkedMultiValueMap<>();
        if(map.get("grant_type").equals("password")){
            paramsMap.set("username",map.get("username"));
            paramsMap.set("password",map.get("password"));
        }else if(map.get("grant_type").equals("refresh_token")){
            paramsMap.set("refresh_token",map.get("refresh_token"));
        }
        paramsMap.set("grant_type",map.get("grant_type"));
        RestTemplate restTemplate=new RestTemplate();
        restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(map.get("client_id").toString(),map.get("client_secret").toString()));
        OAuth2AccessToken token=restTemplate.postForObject("http://localhost:8080/oauth/token",paramsMap,OAuth2AccessToken.class);
        return new ResponseResult("Login succeeded!",token);
    }

Now let's test the refresh_token again, and the result is as follows:

You can see that refresh_token can also refresh access_token, and also return our custom format. Of course, I want to explain here that the way I use now is to put the client ﹣ ID and client ﹣ secret in the parameters for requests. As mentioned in the previous chapter, I can also use Basic credentials for requests. The specific change is to first obtain Authorization from the request header and see if it starts with "Basic". If it does, put it in the request header of restTemplate. If not, then put it in the request header of restTemplate The client ID and client secret in the parameter are added through restTemplate.getInterceptors(). In addition, the request cannot succeed every time. If there is an exception, take the exception out of the returned token. The specific method is as follows:

		if(token.getValue()==null){
            return new ResponseResult(Integer.parseInt(token.getAdditionalInformation().get("code").toString()),token.getAdditionalInformation().get("msg").toString());
        }else{
            return new ResponseResult("Login succeeded!",token);
        }

The code and msg in the token are also exceptions that I have customized. For example, the password error will display the following information:

For custom exception handling, please wait for details in the next chapter.

3 add additional token information

After we request a token, if the front-end needs to, for example, display the user information on the page, can we add some additional parameters to the token when we request it? The answer is yes. It's also relatively simple. You only need to implement the TokenEnhancer interface. The specific code is as follows:

package com.example.oauth2.service.impl;

import com.example.oauth2.entity.Account;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.Map;

@Service
public class TokenEnhancerImpl implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
        Account account=(Account)oAuth2Authentication.getPrincipal();
        DefaultOAuth2AccessToken token=(DefaultOAuth2AccessToken)oAuth2AccessToken;
        Map<String,Object> map=new LinkedHashMap<>();
        map.put("username",account.getUsername());
        map.put("nickname",account.getNickname());
        token.setAdditionalInformation(map);
        return oAuth2AccessToken;
    }
}

Then add the last line of code in the public void configure (authorizationserverendpoints configurer endpoints) method of AuthorizationServerConfig:

	@Autowired
    @Qualifier("tokenEnhancerImpl")
    private TokenEnhancer tokenEnhancer;

	@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(new InMemoryTokenStore())
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenEnhancer(tokenEnhancer);
    }

Now let's test it:

You can see that two new messages have been added to the data. So far, the custom token work has been completed.

4 Conclusion

I don't know if this usage is the most reasonable one, but as long as it can meet the needs, it doesn't matter how many?
In 2.4 of the previous chapter, we have a code like @ EnableGlobalMethodSecurity(prePostEnabled = true). We will learn more about permission control and exception handling in the next chapter.
I hope China can overcome the epidemic as soon as possible. Come on!

Published 2 original articles, praised 0 and visited 13
Private letter follow

Posted by mattcooper on Thu, 13 Feb 2020 01:04:10 -0800