一. 底层原理
通过浏览器/外部服务传入账号密码,以及clientId以及secret申请token,TokenEndpoint 即为校验并颁发Token的方法。
org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
-- postAccessToken()方法
源码如下:
@RequestMapping(
value = {"/oauth/token"},
method = {RequestMethod.POST}
)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
} else {
if (authenticatedClient != null) {
this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}
if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
} else if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
} else {
if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
this.logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.emptySet());
}
if (this.isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
}
OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
} else {
return this.getResponse(token);
}
}
}
}
}
校验Token是否合法的oauth实现类源码如下:
package org.springframework.security.oauth2.provider.endpoint;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@FrameworkEndpoint
public class CheckTokenEndpoint {
private ResourceServerTokenServices resourceServerTokenServices;
private AccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
protected final Log logger = LogFactory.getLog(this.getClass());
private WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator = new DefaultWebResponseExceptionTranslator();
public CheckTokenEndpoint(ResourceServerTokenServices resourceServerTokenServices) {
this.resourceServerTokenServices = resourceServerTokenServices;
}
public void setExceptionTranslator(WebResponseExceptionTranslator<OAuth2Exception> exceptionTranslator) {
this.exceptionTranslator = exceptionTranslator;
}
public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
this.accessTokenConverter = accessTokenConverter;
}
@RequestMapping({"/oauth/check_token"})
@ResponseBody
public Map<String, ?> checkToken(@RequestParam("token") String value) {
OAuth2AccessToken token = this.resourceServerTokenServices.readAccessToken(value);
if (token == null) {
throw new InvalidTokenException("Token was not recognised");
} else if (token.isExpired()) {
throw new InvalidTokenException("Token has expired");
} else {
OAuth2Authentication authentication = this.resourceServerTokenServices.loadAuthentication(token.getValue());
Map<String, Object> response = this.accessTokenConverter.convertAccessToken(token, authentication);
response.put("active", true);
return response;
}
}
@ExceptionHandler({InvalidTokenException.class})
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
this.logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
InvalidTokenException e400 = new InvalidTokenException(e.getMessage()) {
public int getHttpErrorCode() {
return 400;
}
};
return this.exceptionTranslator.translate(e400);
}
}
/oauth/check_token就是资源服务器请求oauth2.0授权服务器进行token认证的方法。
二. 实现自定义颁发token代码
/**
* 生成 oauth2 token
*
* @param userName 账号
* @param password 密码
* @param type 登录类型
* @return 返回的令牌
*/
@Override
public JSONObject getToken(String userName, String password, String type, HttpHeaders headers) {
// 读取 yml文件中的clientId和secret
OpenOAuth2ClientDetails clientDetails = clientProperties.getOauth2().get("admin");
// 调用TokenEndpoint中的postAccessToken()方法
String url = WebUtils.getServerUrl(WebUtils.getHttpServletRequest()) + "/oauth/token";
// 使用oauth2密码模式登录.
MultiValueMap<String, Object> postParameters = new LinkedMultiValueMap<>();
postParameters.add(OAUTH2_USERNAME, userName);
postParameters.add(OAUTH2_PASSWORD, password);
postParameters.add(OAUTH2_CLIENT_ID, clientDetails.getClientId());
postParameters.add(OAUTH2_CLIENT_SECRET, clientDetails.getClientSecret());
postParameters.add(OAUTH2_GRANT_TYPE, OAUTH2_PASSWORD);
// 添加参数区分,第三方登录
postParameters.add(OAUTH2_LOGIN_TYPE, type);
// 使用客户端的请求头,发起请求
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// 强制移除 原来的请求头,防止token失效
headers.remove(HttpHeaders.AUTHORIZATION);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(postParameters, headers);
JSONObject result = restTemplate.postForObject(url, request, JSONObject.class);
log.debug("getToken url:[{}]", url);
return result;
}
调用成功后,直接返回token,即满足自定义颁发token的需求。