由于项目OAuth2采用了多种模式,授权码模式为第三方系统接入,密码模式用于用户登录,Client模式用于服务间调用,
所有不同的模式下的token需要用 @PreAuthorize("hasAuthority('client')") 进行隔离,遇到问题一直验证不通过。
通过调试发现资源服务从授权服务拿到的authrities字段一直为空, StackOverFlow说低版本(项目中才2.0.15)的OAuth2实现权限隔离需要 重写UserInfoTokenService
但是资源服务太多所以考虑重写授权服务的返回值,如何重写?在哪里重写?是下面要介绍的~
一、哪里重写?
资源服务器向授权服务服务器获取资源时候,返回的user信息重写,加入authorities
@RestController @Slf4j public class UserController { @Autowired HttpServletRequest request; @GetMapping("/user") public Principal user(Principal principal) { log.info("获取user信息:{}", JSON.toJSON(principal));
return principal;
}
返回的具体用户信息:
1 { 2 "principal": { 3 "password": "$2a$10$OjTFAZEzS6qypY4nRZtnM.MzS6F3XsIlkAO/kIFCu30kAk8Yasowa", 4 "phone": "13918438965", 5 "credentialsNonExpired": true, 6 "accountNonExpired": true, 7 "enabled": true, 8 "accountNonLocked": true, 9 "username": "4738195728608789333" 10 }, 11 "authenticated": true, 12 "oAuth2Request": { 13 "redirectUri": "http://www.baidu.com", 14 "responseTypes": ["code"], 15 "approved": true, 16 "extensions": {}, 17 "clientId": "external", 18 "scope": ["auth_base"], 19 "requestParameters": { 20 "code": "ovzMSk", 21 "grant_type": "authorization_code", 22 "scope": "auth_base", 23 "response_type": "code", 24 "redirect_uri": "http://www.baidu.com", 25 "state": "123", 26 "client_secret": "D524C1A0811DA49592F841085CC0063EB62B3001252A9454", 27 "client_id": "external" 28 }, 29 "refresh": false, 30 "grantType": "authorization_code", 31 "authorities": [{ 32 "authority": "auth_base" 33 }], 34 "resourceIds": [] 35 }, 36 "clientOnly": false, 37 "credentials": "", 38 "name": "4738195728608789333", 39 "userAuthentication": { 40 "principal": { 41 "password": "$2a$10$OjTFAZEzS6qypY4nRZtnM.MzS6F3XsIlkAO/kIFCu30kAk8Yasowa", 42 "phone": "13918438965", 43 "credentialsNonExpired": true, 44 "accountNonExpired": true, 45 "enabled": true, 46 "accountNonLocked": true, 47 "username": "4738195728608789333" 48 }, 49 "authenticated": true, 50 "oAuth2Request": { 51 "responseTypes": [], 52 "approved": true, 53 "extensions": {}, 54 "clientId": "gt", 55 "scope": ["frontend"], 56 "requestParameters": { 57 "auth_type": "sms", 58 "device_id": "5c5d1d7b-50ae-4347-9aee-7a7686055f4d", 59 "grant_type": "password", 60 "client_id": "gt", 61 "username": "13918438965" 62 }, 63 "refresh": false, 64 "grantType": "password", 65 "authorities": [{ 66 "authority": "client" 67 }], 68 "resourceIds": [] 69 }, 70 "clientOnly": false, 71 "credentials": "", 72 "name": "4738195728608789333", 73 "userAuthentication": { 74 "principal": { 75 "password": "$2a$10$OjTFAZEzS6qypY4nRZtnM.MzS6F3XsIlkAO/kIFCu30kAk8Yasowa", 76 "phone": "13918438965", 77 "credentialsNonExpired": true, 78 "accountNonExpired": true, 79 "enabled": true, 80 "accountNonLocked": true, 81 "username": "4738195728608789333" 82 }, 83 "authenticated": true, 84 "name": "4738195728608789333", 85 "details": { 86 "auth_type": "sms", 87 "device_id": "5c5d1d7b-50ae-4347-9aee-7a7686055f4d", 88 "grant_type": "password", 89 "client_secret": "D524C1A0811DA49592F841085CC0063EB62B3001252A94542795D1CA9824A941", 90 "client_id": "gt", 91 "username": "13918438965" 92 }, 93 "authorities": [] 94 }, 95 "details": { 96 "tokenType": "Bearer", 97 "tokenValue": "f7870e71-7b0f-4a4a-9c6f-bb6d1f903ad9", 98 "remoteAddress": "0:0:0:0:0:0:0:1" 99 }, 100 "authorities": [] 101 }, 102 "details": { 103 "tokenType": "Bearer", 104 "tokenValue": "7829005c-5ebe-4428-b951-89477b24316e", 105 "remoteAddress": "0:0:0:0:0:0:0:1" 106 }, 107 "authorities": [] 108 }
二、如何重写?
principal是OAuth2Authentication实例,OAuth2Authentication主要包括OAuth2Request storedRequest、Authentication userAuthentication,
重写目的是将storedRequest authorities复制到authoritie中,但问题是authoritie不让修改的,没办法只能重写这个OAuth2Authentication了。
为了改变authoritie重写:
@GetMapping("/user") public Principal user(Principal principal) { log.info("获取user信息:{}", JSON.toJSON(principal)); OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) principal; OAuth2Request storedRequest = oAuth2Authentication.getOAuth2Request(); Authentication userAuthentication = oAuth2Authentication.getUserAuthentication(); // 为了服务端进行token权限隔离 定制OAuth2Authentication CustomOAuth2Authentication customOAuth2Authentication = new CustomOAuth2Authentication(storedRequest, userAuthentication, storedRequest.getAuthorities()); customOAuth2Authentication.setDetails(oAuth2Authentication.getDetails()); log.info("返回用户信息:{}", JSON.toJSON(customOAuth2Authentication)); return customOAuth2Authentication; }
CustomOAuth2Authentication :
1 package com.brightcns.wuxi.citizencard.auth.domain; 2 3 import org.springframework.security.authentication.AbstractAuthenticationToken; 4 import org.springframework.security.core.Authentication; 5 import org.springframework.security.core.CredentialsContainer; 6 import org.springframework.security.core.GrantedAuthority; 7 import org.springframework.security.oauth2.provider.OAuth2Request; 8 9 import java.util.Collection; 10 11 /** 12 * @author maxianming 13 * @date 2018/10/29 13:53 14 */ 15 public class CustomOAuth2Authentication extends AbstractAuthenticationToken { 16 17 private static final long serialVersionUID = -4809832298438307309L; 18 19 private final OAuth2Request storedRequest; 20 21 private final Authentication userAuthentication; 22 23 /** 24 * Construct an OAuth 2 authentication. Since some grant types don't require user authentication, the user 25 * authentication may be null. 26 * @param storedRequest The authorization request (must not be null). 27 * @param userAuthentication The user authentication (possibly null). 28 */ 29 public CustomOAuth2Authentication(OAuth2Request storedRequest, Authentication userAuthentication, Collection<? extends GrantedAuthority> authorities) { 30 /** 31 * 为了服务端进行token权限隔离 {@link @PreAuthorize("hasAuthority('server')")},自定义OAuth2Authentication使得支持改变authorities 32 */ 33 super(authorities != null ? authorities : userAuthentication == null ? storedRequest.getAuthorities() : userAuthentication.getAuthorities()); 34 this.storedRequest = storedRequest; 35 this.userAuthentication = userAuthentication; 36 } 37 38 public Object getCredentials() { 39 return ""; 40 } 41 42 public Object getPrincipal() { 43 return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication 44 .getPrincipal(); 45 } 46 47 /** 48 * Convenience method to check if there is a user associated with this token, or just a client application. 49 * 50 * @return true if this token represents a client app not acting on behalf of a user 51 */ 52 public boolean isClientOnly() { 53 return userAuthentication == null; 54 } 55 56 /** 57 * The authorization request containing details of the client application. 58 * 59 * @return The client authentication. 60 */ 61 public OAuth2Request getOAuth2Request() { 62 return storedRequest; 63 } 64 65 /** 66 * The user authentication. 67 * 68 * @return The user authentication. 69 */ 70 public Authentication getUserAuthentication() { 71 return userAuthentication; 72 } 73 74 @Override 75 public boolean isAuthenticated() { 76 return this.storedRequest.isApproved() 77 && (this.userAuthentication == null || this.userAuthentication.isAuthenticated()); 78 } 79 80 @Override 81 public void eraseCredentials() { 82 super.eraseCredentials(); 83 if (this.userAuthentication != null && CredentialsContainer.class.isAssignableFrom(this.userAuthentication.getClass())) { 84 CredentialsContainer.class.cast(this.userAuthentication).eraseCredentials(); 85 } 86 } 87 88 @Override 89 public boolean equals(Object o) { 90 if (this == o) { 91 return true; 92 } 93 if (!(o instanceof CustomOAuth2Authentication)) { 94 return false; 95 } 96 if (!super.equals(o)) { 97 return false; 98 } 99 100 CustomOAuth2Authentication that = (CustomOAuth2Authentication) o; 101 102 if (!storedRequest.equals(that.storedRequest)) { 103 return false; 104 } 105 if (userAuthentication != null ? !userAuthentication.equals(that.userAuthentication) 106 : that.userAuthentication != null) { 107 return false; 108 } 109 110 if (getDetails() != null ? !getDetails().equals(that.getDetails()) : that.getDetails() != null) { 111 // return false; 112 } 113 114 return true; 115 } 116 117 @Override 118 public int hashCode() { 119 int result = super.hashCode(); 120 result = 31 * result + storedRequest.hashCode(); 121 result = 31 * result + (userAuthentication != null ? userAuthentication.hashCode() : 0); 122 return result; 123 } 124 125 }
主要在OAuth2Authentication基础上修改了30-35行代码