View Javadoc
1   /*
2    * Copyright 2013-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5    * the License. You may obtain a copy of the License at
6    *
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11   * specific language governing permissions and limitations under the License.
12   */
13  
14  package org.springframework.security.oauth2.provider.token.store;
15  
16  import org.springframework.security.core.Authentication;
17  import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
18  import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
19  import org.springframework.security.oauth2.common.OAuth2AccessToken;
20  import org.springframework.security.oauth2.common.OAuth2RefreshToken;
21  import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
22  import org.springframework.security.oauth2.provider.OAuth2Authentication;
23  import org.springframework.security.oauth2.provider.approval.Approval;
24  import org.springframework.security.oauth2.provider.approval.Approval.ApprovalStatus;
25  import org.springframework.security.oauth2.provider.approval.ApprovalStore;
26  import org.springframework.security.oauth2.provider.token.TokenStore;
27  
28  import java.util.*;
29  
30  /**
31   * A {@link TokenStore} implementation that just reads data from the tokens themselves. Not really a store since it
32   * never persists anything, and methods like {@link #getAccessToken(OAuth2Authentication)} always return null. But
33   * nevertheless a useful tool since it translates access tokens to and from authentications. Use this wherever a
34   * {@link TokenStore} is needed, but remember to use the same {@link JwtAccessTokenConverter} instance (or one with the same
35   * verifier) as was used when the tokens were minted.
36   * 
37   * @author Dave Syer
38   *
39   */
40  public class JwtTokenStore implements TokenStore {
41  
42  	private JwtAccessTokenConverter jwtTokenEnhancer;
43  
44  	private ApprovalStore approvalStore;
45  
46  	/**
47  	 * Create a JwtTokenStore with this token enhancer (should be shared with the DefaultTokenServices if used).
48  	 * 
49  	 * @param jwtTokenEnhancer
50  	 */
51  	public JwtTokenStore(JwtAccessTokenConverter jwtTokenEnhancer) {
52  		this.jwtTokenEnhancer = jwtTokenEnhancer;
53  	}
54  
55  	/**
56  	 * ApprovalStore to be used to validate and restrict refresh tokens.
57  	 * 
58  	 * @param approvalStore the approvalStore to set
59  	 */
60  	public void setApprovalStore(ApprovalStore approvalStore) {
61  		this.approvalStore = approvalStore;
62  	}
63  
64  	@Override
65  	public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
66  		return readAuthentication(token.getValue());
67  	}
68  
69  	@Override
70  	public OAuth2Authentication readAuthentication(String token) {
71  		return jwtTokenEnhancer.extractAuthentication(jwtTokenEnhancer.decode(token));
72  	}
73  
74  	@Override
75  	public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
76  	}
77  
78  	@Override
79  	public OAuth2AccessToken readAccessToken(String tokenValue) {
80  		OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
81  		if (jwtTokenEnhancer.isRefreshToken(accessToken)) {
82  			throw new InvalidTokenException("Encoded token is a refresh token");
83  		}
84  		return accessToken;
85  	}
86  
87  	private OAuth2AccessToken convertAccessToken(String tokenValue) {
88  		return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
89  	}
90  
91  	@Override
92  	public void removeAccessToken(OAuth2AccessToken token) {
93  		// gh-807 Approvals (if any) should only be removed when Refresh Tokens are removed (or expired)
94  	}
95  
96  	@Override
97  	public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
98  	}
99  
100 	@Override
101 	public OAuth2RefreshToken readRefreshToken(String tokenValue) {
102 		OAuth2AccessToken encodedRefreshToken = convertAccessToken(tokenValue);
103 		OAuth2RefreshToken refreshToken = createRefreshToken(encodedRefreshToken);
104 		if (approvalStore != null) {
105 			OAuth2Authentication authentication = readAuthentication(tokenValue);
106 			if (authentication.getUserAuthentication() != null) {
107 				String userId = authentication.getUserAuthentication().getName();
108 				String clientId = authentication.getOAuth2Request().getClientId();
109 				Collection<Approval> approvals = approvalStore.getApprovals(userId, clientId);
110 				Collection<String> approvedScopes = new HashSet<String>();
111 				for (Approval approval : approvals) {
112 					if (approval.isApproved()) {
113 						approvedScopes.add(approval.getScope());
114 					}
115 				}
116 				if (!approvedScopes.containsAll(authentication.getOAuth2Request().getScope())) {
117 					return null;
118 				}
119 			}
120 		}
121 		return refreshToken;
122 	}
123 
124 	private OAuth2RefreshToken createRefreshToken(OAuth2AccessToken encodedRefreshToken) {
125 		if (!jwtTokenEnhancer.isRefreshToken(encodedRefreshToken)) {
126 			throw new InvalidTokenException("Encoded token is not a refresh token");
127 		}
128 		if (encodedRefreshToken.getExpiration()!=null) {
129 			return new DefaultExpiringOAuth2RefreshToken(encodedRefreshToken.getValue(),
130 					encodedRefreshToken.getExpiration());			
131 		}
132 		return new DefaultOAuth2RefreshToken(encodedRefreshToken.getValue());
133 	}
134 
135 	@Override
136 	public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
137 		return readAuthentication(token.getValue());
138 	}
139 
140 	@Override
141 	public void removeRefreshToken(OAuth2RefreshToken token) {
142 		remove(token.getValue());
143 	}
144 
145 	@Override
146 	public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
147 		// gh-807 Approvals (if any) should only be removed when Refresh Tokens are removed (or expired)
148 	}
149 
150 	@Override
151 	public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
152 		// We don't want to accidentally issue a token, and we have no way to reconstruct the refresh token
153 		return null;
154 	}
155 
156 	@Override
157 	public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
158 		return Collections.emptySet();
159 	}
160 
161 	@Override
162 	public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
163 		return Collections.emptySet();
164 	}
165 
166 	public void setTokenEnhancer(JwtAccessTokenConverter tokenEnhancer) {
167 		this.jwtTokenEnhancer = tokenEnhancer;
168 	}
169 
170 	private void remove(String token) {
171 		if (approvalStore != null) {
172 			OAuth2Authentication auth = readAuthentication(token);
173 			String clientId = auth.getOAuth2Request().getClientId();
174 			Authentication user = auth.getUserAuthentication();
175 			if (user != null) {
176 				Collection<Approval> approvals = new ArrayList<Approval>();
177 				for (String scope : auth.getOAuth2Request().getScope()) {
178 					approvals.add(new Approval(user.getName(), clientId, scope, new Date(), ApprovalStatus.APPROVED));
179 				}
180 				approvalStore.revokeApprovals(approvals);
181 			}
182 		}
183 	}
184 }