背景

整个项目设置了统一的返回数据格式,需要修改资源服务器获取认证中心的用户信息后的解析。因为资源服务器和zuul网关是整合为一个服务的,向下游的服务转发时需要携带用户id。所以也需要修改线程保存的用户信息。

spring cloud 认证大致思路

当用户携带token访问zuul网关时经过zuul过滤器,因为需要认证而转发到认证服务器,认证服务使用rabbion组件访问认证中心获取用户信息(调用类为RemoteTokenServices),使用类 AccessTokenConverter(默认实现类 DefaultAccessTokenConverter)解析token,具体解析封装在类 UserAuthenticationConverter(默认实现为 DefaultUserAuthenticationConverter),然后经过认证将用户信息(user_name)放入 SecurityContextHolder.setContext(SecurityContext context) 的 authentication() 实例中,以供程序获取当前用户信息。

修改思路

重写 RemoteTokenServices 、 AccessTokenConverter 和 UserAuthenticationConverter的实现类。将用户id放入SecurityContext中。

MyRemoteTokenServices.java

  1. package com.boya.web.config.oauth;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.util.Map;
  5. import org.apache.commons.logging.Log;
  6. import org.apache.commons.logging.LogFactory;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.http.HttpEntity;
  9. import org.springframework.http.HttpHeaders;
  10. import org.springframework.http.HttpMethod;
  11. import org.springframework.http.MediaType;
  12. import org.springframework.http.client.ClientHttpResponse;
  13. import org.springframework.security.core.AuthenticationException;
  14. import org.springframework.security.oauth2.common.OAuth2AccessToken;
  15. import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
  16. import org.springframework.security.oauth2.provider.OAuth2Authentication;
  17. import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
  18. import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
  19. import org.springframework.util.LinkedMultiValueMap;
  20. import org.springframework.util.MultiValueMap;
  21. import org.springframework.web.client.DefaultResponseErrorHandler;
  22. import org.springframework.web.client.RestOperations;
  23. import org.springframework.web.client.RestTemplate;
  24. import com.alibaba.fastjson.JSON;
  25. import com.alibaba.fastjson.JSONObject;
  26. import com.boya.config.response.HttpResponseBase;
  27. //@Primary
  28. //@Service
  29. public class MyRemoteTokenServices implements ResourceServerTokenServices {
  30. protected final Log logger = LogFactory.getLog(getClass());
  31. private RestOperations restTemplate;
  32. @Value("${security.oauth2.resource.token-info-uri}")
  33. private String checkTokenEndpointUrl;
  34. @Value("${security.oauth2.client.client-id}")
  35. private String clientId;
  36. @Value("${security.oauth2.client.client-secret}")
  37. private String clientSecret;
  38. private String tokenName = "token";
  39. //使用自定义的AccessTokenConverter
  40. private AccessTokenConverter tokenConverter = new MyAccessTokenConverter();
  41. public MyRemoteTokenServices() {
  42. restTemplate = new RestTemplate();
  43. ((RestTemplate) restTemplate).setErrorHandler(new DefaultResponseErrorHandler() {
  44. @Override
  45. // Ignore 400
  46. public void handleError(ClientHttpResponse response) throws IOException {
  47. if (response.getRawStatusCode() != 400) {
  48. super.handleError(response);
  49. }
  50. }
  51. });
  52. }
  53. public void setRestTemplate(RestOperations restTemplate) {
  54. this.restTemplate = restTemplate;
  55. }
  56. public void setCheckTokenEndpointUrl(String checkTokenEndpointUrl) {
  57. this.checkTokenEndpointUrl = checkTokenEndpointUrl;
  58. }
  59. public void setClientId(String clientId) {
  60. this.clientId = clientId;
  61. }
  62. public void setClientSecret(String clientSecret) {
  63. this.clientSecret = clientSecret;
  64. }
  65. public void setAccessTokenConverter(AccessTokenConverter accessTokenConverter) {
  66. this.tokenConverter = accessTokenConverter;
  67. }
  68. public void setTokenName(String tokenName) {
  69. this.tokenName = tokenName;
  70. }
  71. @Override
  72. public OAuth2Authentication loadAuthentication(String accessToken)
  73. throws AuthenticationException, InvalidTokenException {
  74. MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
  75. formData.add(tokenName, accessToken);
  76. HttpHeaders headers = new HttpHeaders();
  77. headers.set("Authorization", getAuthorizationHeader(clientId, clientSecret));
  78. Map<String, Object> map = postForMap(checkTokenEndpointUrl, formData, headers);
  79. if (logger.isDebugEnabled()) {
  80. logger.debug("check_token returned result: " + JSON.toJSONString(map));
  81. }
  82. HttpResponseBase httpResponse = JSON.parseObject(JSON.toJSONString(map), HttpResponseBase.class);
  83. JSONObject rsJson = (JSONObject) httpResponse.getResults();
  84. if ("invalid_token".equalsIgnoreCase(httpResponse.getCode())
  85. || (rsJson != null && rsJson.containsKey("error"))) {
  86. if (logger.isDebugEnabled()) {
  87. logger.debug("check_token returned error: " + map.get("error"));
  88. }
  89. throw new InvalidTokenException(accessToken);
  90. }
  91. if (!Boolean.TRUE.equals(rsJson.get("active"))) {
  92. logger.debug("check_token returned active attribute: " + map.get("active"));
  93. throw new InvalidTokenException(accessToken);
  94. }
  95. return tokenConverter.extractAuthentication(rsJson);
  96. }
  97. @Override
  98. public OAuth2AccessToken readAccessToken(String accessToken) {
  99. throw new UnsupportedOperationException("Not supported: read access token");
  100. }
  101. private String getAuthorizationHeader(String clientId, String clientSecret) {
  102. if (clientId == null || clientSecret == null) {
  103. logger.warn(
  104. "Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.");
  105. }
  106. String creds = String.format("%s:%s", clientId, clientSecret);
  107. try {
  108. return "Basic " + new String(java.util.Base64.getEncoder().encode(creds.getBytes("UTF-8")));
  109. } catch (UnsupportedEncodingException e) {
  110. throw new IllegalStateException("Could not convert String");
  111. }
  112. }
  113. private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
  114. if (headers.getContentType() == null) {
  115. headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
  116. }
  117. @SuppressWarnings("rawtypes")
  118. Map map = restTemplate.exchange(path, HttpMethod.POST,
  119. new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
  120. @SuppressWarnings("unchecked")
  121. Map<String, Object> result = map;
  122. return result;
  123. }
  124. }

MyAccessTokenConverter.java

  1. package com.boya.web.config.oauth;
  2. import java.util.Arrays;
  3. import java.util.Collection;
  4. import java.util.Collections;
  5. import java.util.Date;
  6. import java.util.HashMap;
  7. import java.util.LinkedHashSet;
  8. import java.util.Map;
  9. import java.util.Set;
  10. import org.springframework.security.core.Authentication;
  11. import org.springframework.security.core.GrantedAuthority;
  12. import org.springframework.security.core.authority.AuthorityUtils;
  13. import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
  14. import org.springframework.security.oauth2.common.OAuth2AccessToken;
  15. import org.springframework.security.oauth2.provider.OAuth2Authentication;
  16. import org.springframework.security.oauth2.provider.OAuth2Request;
  17. import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
  18. import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
  19. /**
  20. * Default implementation of {@link AccessTokenConverter}.
  21. *
  22. * @author Dave Syer
  23. * @author Vedran Pavic
  24. */
  25. public class MyAccessTokenConverter implements AccessTokenConverter {
  26. //使用自定义UserAuthenticationConverter
  27. private UserAuthenticationConverter userTokenConverter = new MyUserAuthenticationConverter();
  28. private boolean includeGrantType;
  29. private String scopeAttribute = SCOPE;
  30. private String clientIdAttribute = CLIENT_ID;
  31. /**
  32. * Converter for the part of the data in the token representing a user.
  33. *
  34. * @param userTokenConverter the userTokenConverter to set
  35. */
  36. public void setUserTokenConverter(UserAuthenticationConverter userTokenConverter) {
  37. this.userTokenConverter = userTokenConverter;
  38. }
  39. /**
  40. * Flag to indicate the the grant type should be included in the converted token.
  41. *
  42. * @param includeGrantType the flag value (default false)
  43. */
  44. public void setIncludeGrantType(boolean includeGrantType) {
  45. this.includeGrantType = includeGrantType;
  46. }
  47. /**
  48. * Set scope attribute name to be used in the converted token. Defaults to
  49. * {@link AccessTokenConverter#SCOPE}.
  50. *
  51. * @param scopeAttribute the scope attribute name to use
  52. */
  53. public void setScopeAttribute(String scopeAttribute) {
  54. this.scopeAttribute = scopeAttribute;
  55. }
  56. /**
  57. * Set client id attribute name to be used in the converted token. Defaults to
  58. * {@link AccessTokenConverter#CLIENT_ID}.
  59. *
  60. * @param clientIdAttribute the client id attribute name to use
  61. */
  62. public void setClientIdAttribute(String clientIdAttribute) {
  63. this.clientIdAttribute = clientIdAttribute;
  64. }
  65. public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
  66. Map<String, Object> response = new HashMap<String, Object>();
  67. OAuth2Request clientToken = authentication.getOAuth2Request();
  68. if (!authentication.isClientOnly()) {
  69. response.putAll(userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication()));
  70. } else {
  71. if (clientToken.getAuthorities()!=null && !clientToken.getAuthorities().isEmpty()) {
  72. response.put(UserAuthenticationConverter.AUTHORITIES,
  73. AuthorityUtils.authorityListToSet(clientToken.getAuthorities()));
  74. }
  75. }
  76. if (token.getScope()!=null) {
  77. response.put(scopeAttribute, token.getScope());
  78. }
  79. if (token.getAdditionalInformation().containsKey(JTI)) {
  80. response.put(JTI, token.getAdditionalInformation().get(JTI));
  81. }
  82. if (token.getExpiration() != null) {
  83. response.put(EXP, token.getExpiration().getTime() / 1000);
  84. }
  85. if (includeGrantType && authentication.getOAuth2Request().getGrantType()!=null) {
  86. response.put(GRANT_TYPE, authentication.getOAuth2Request().getGrantType());
  87. }
  88. response.putAll(token.getAdditionalInformation());
  89. response.put(clientIdAttribute, clientToken.getClientId());
  90. if (clientToken.getResourceIds() != null && !clientToken.getResourceIds().isEmpty()) {
  91. response.put(AUD, clientToken.getResourceIds());
  92. }
  93. return response;
  94. }
  95. public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
  96. DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(value);
  97. Map<String, Object> info = new HashMap<String, Object>(map);
  98. info.remove(EXP);
  99. info.remove(AUD);
  100. info.remove(clientIdAttribute);
  101. info.remove(scopeAttribute);
  102. if (map.containsKey(EXP)) {
  103. token.setExpiration(new Date((Long) map.get(EXP) * 1000L));
  104. }
  105. if (map.containsKey(JTI)) {
  106. info.put(JTI, map.get(JTI));
  107. }
  108. token.setScope(extractScope(map));
  109. token.setAdditionalInformation(info);
  110. return token;
  111. }
  112. public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
  113. Map<String, String> parameters = new HashMap<String, String>();
  114. Set<String> scope = extractScope(map);
  115. Authentication user = userTokenConverter.extractAuthentication(map);
  116. String clientId = (String) map.get(clientIdAttribute);
  117. parameters.put(clientIdAttribute, clientId);
  118. if (includeGrantType && map.containsKey(GRANT_TYPE)) {
  119. parameters.put(GRANT_TYPE, (String) map.get(GRANT_TYPE));
  120. }
  121. Set<String> resourceIds = new LinkedHashSet<String>(map.containsKey(AUD) ? getAudience(map)
  122. : Collections.<String>emptySet());
  123. Collection<? extends GrantedAuthority> authorities = null;
  124. if (user==null && map.containsKey(AUTHORITIES)) {
  125. @SuppressWarnings("unchecked")
  126. String[] roles = ((Collection<String>)map.get(AUTHORITIES)).toArray(new String[0]);
  127. authorities = AuthorityUtils.createAuthorityList(roles);
  128. }
  129. OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null,
  130. null);
  131. return new OAuth2Authentication(request, user);
  132. }
  133. private Collection<String> getAudience(Map<String, ?> map) {
  134. Object auds = map.get(AUD);
  135. if (auds instanceof Collection) {
  136. @SuppressWarnings("unchecked")
  137. Collection<String> result = (Collection<String>) auds;
  138. return result;
  139. }
  140. return Collections.singleton((String)auds);
  141. }
  142. private Set<String> extractScope(Map<String, ?> map) {
  143. Set<String> scope = Collections.emptySet();
  144. if (map.containsKey(scopeAttribute)) {
  145. Object scopeObj = map.get(scopeAttribute);
  146. if (String.class.isInstance(scopeObj)) {
  147. scope = new LinkedHashSet<String>(Arrays.asList(String.class.cast(scopeObj).split(" ")));
  148. } else if (Collection.class.isAssignableFrom(scopeObj.getClass())) {
  149. @SuppressWarnings("unchecked")
  150. Collection<String> scopeColl = (Collection<String>) scopeObj;
  151. scope = new LinkedHashSet<String>(scopeColl); // Preserve ordering
  152. }
  153. }
  154. return scope;
  155. }
  156. }

MyUserAuthenticationConverter.java

  1. package com.boya.web.config.oauth;
  2. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  3. import org.springframework.security.core.Authentication;
  4. import org.springframework.security.core.GrantedAuthority;
  5. import org.springframework.security.core.authority.AuthorityUtils;
  6. import org.springframework.security.core.userdetails.UserDetails;
  7. import org.springframework.security.core.userdetails.UserDetailsService;
  8. import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
  9. import org.springframework.util.StringUtils;
  10. import java.util.Collection;
  11. import java.util.LinkedHashMap;
  12. import java.util.Map;
  13. /**
  14. * Default implementation of {@link UserAuthenticationConverter}. Converts to and from an Authentication using only its
  15. * name and authorities.
  16. *
  17. * @author Dave Syer
  18. *
  19. */
  20. public class MyUserAuthenticationConverter implements UserAuthenticationConverter {
  21. private Collection<? extends GrantedAuthority> defaultAuthorities;
  22. private UserDetailsService userDetailsService;
  23. //新增获取参数
  24. private String USERID = "userId";
  25. /**
  26. * Optional {@link UserDetailsService} to use when extracting an {@link Authentication} from the incoming map.
  27. *
  28. * @param userDetailsService the userDetailsService to set
  29. */
  30. public void setUserDetailsService(UserDetailsService userDetailsService) {
  31. this.userDetailsService = userDetailsService;
  32. }
  33. /**
  34. * Default value for authorities if an Authentication is being created and the input has no data for authorities.
  35. * Note that unless this property is set, the default Authentication created by {@link #extractAuthentication(Map)}
  36. * will be unauthenticated.
  37. *
  38. * @param defaultAuthorities the defaultAuthorities to set. Default null.
  39. */
  40. public void setDefaultAuthorities(String[] defaultAuthorities) {
  41. this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
  42. .arrayToCommaDelimitedString(defaultAuthorities));
  43. }
  44. public Map<String, ?> convertUserAuthentication(Authentication authentication) {
  45. Map<String, Object> response = new LinkedHashMap<String, Object>();
  46. response.put(USERNAME, authentication.getName());
  47. if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
  48. response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
  49. }
  50. return response;
  51. }
  52. public Authentication extractAuthentication(Map<String, ?> map) {
  53. //新增先获取userId
  54. if (map.containsKey(USERID)) {
  55. Object principal = map.get(USERID);
  56. Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
  57. if (userDetailsService != null) {
  58. UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERID));
  59. authorities = user.getAuthorities();
  60. principal = user;
  61. }
  62. return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
  63. }
  64. if (map.containsKey(USERNAME)) {
  65. Object principal = map.get(USERNAME);
  66. Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
  67. if (userDetailsService != null) {
  68. UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
  69. authorities = user.getAuthorities();
  70. principal = user;
  71. }
  72. return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
  73. }
  74. return null;
  75. }
  76. private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
  77. if (!map.containsKey(AUTHORITIES)) {
  78. return defaultAuthorities;
  79. }
  80. Object authorities = map.get(AUTHORITIES);
  81. if (authorities instanceof String) {
  82. return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
  83. }
  84. if (authorities instanceof Collection) {
  85. return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
  86. .collectionToCommaDelimitedString((Collection<?>) authorities));
  87. }
  88. throw new IllegalArgumentException("Authorities must be either a String or a Collection");
  89. }
  90. }

zuul网关下游服务调用时修改

ModifyParamFilter.java

  1. package com.boya.web.config;
  2. import com.alibaba.fastjson.JSON;
  3. import com.netflix.zuul.ZuulFilter;
  4. import com.netflix.zuul.context.RequestContext;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.security.core.Authentication;
  7. import org.springframework.security.core.context.SecurityContext;
  8. import org.springframework.security.core.context.SecurityContextHolder;
  9. import org.springframework.stereotype.Component;
  10. import javax.servlet.http.HttpServletRequest;
  11. import java.util.*;
  12. @Component
  13. @Slf4j
  14. public class ModifyHeaderFilter extends ZuulFilter {
  15. //定义filter的类型,有pre、route、post、error四种
  16. @Override
  17. public String filterType() {
  18. return "route";
  19. }
  20. //定义filter的顺序,数字越小表示顺序越高,越先执行
  21. @Override
  22. public int filterOrder() {
  23. return 6;
  24. }
  25. //表示是否需要执行该filter,true表示执行,false表示不执行
  26. @Override
  27. public boolean shouldFilter() {
  28. return true;
  29. }
  30. public Object run() {
  31. System.out.println("------------------------------------------------OAuth2AccessToken---");
  32. SecurityContext context = SecurityContextHolder.getContext();
  33. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  34. String currentPrincipalName = authentication.getName();
  35. System.out.println("当前用户-------"+currentPrincipalName);
  36. RequestContext ctx = RequestContext.getCurrentContext();
  37. HttpServletRequest request = ctx.getRequest();
  38. log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
  39. Enumeration<String> headerNames = request.getHeaderNames();
  40. Map<String, Object> headMap = new HashMap<>();
  41. String pa = null;
  42. while ((pa = headerNames.nextElement()) != null) {
  43. headMap.put(pa, request.getHeader(pa));
  44. }
  45. log.info(String.format("header 参数 >>> %s", JSON.toJSONString(headMap)));
  46. Map<String, String[]> requestMap = request.getParameterMap();
  47. log.info(String.format("request 参数 >>> %s", JSON.toJSONString(requestMap)));
  48. Map<String, List<String>> requestQueryParams = ctx.getRequestQueryParams();
  49. if (requestQueryParams==null) {
  50. requestQueryParams=new HashMap<>();
  51. }
  52. //将要新增的参数添加进去,被调用的微服务可以直接 去取,就想普通的一样,框架会直接注入进去
  53. ArrayList<String> arrayList = new ArrayList<>();
  54. arrayList.add(currentPrincipalName);
  55. requestQueryParams.put("userId", arrayList);
  56. // 这个put("requestQueryParams", qp); 是在源码中 会在转发的时候去取这个key里面的参数值.
  57. ctx.setRequestQueryParams(requestQueryParams);
  58. return null;
  59. }
  60. }