View Javadoc
1   /*
2    * Copyright 2006-2011 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  package org.springframework.security.oauth2.provider.client;
14  
15  import java.io.IOException;
16  
17  import javax.servlet.FilterChain;
18  import javax.servlet.ServletException;
19  import javax.servlet.http.HttpServletRequest;
20  import javax.servlet.http.HttpServletResponse;
21  
22  import org.springframework.security.authentication.BadCredentialsException;
23  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
24  import org.springframework.security.core.Authentication;
25  import org.springframework.security.core.AuthenticationException;
26  import org.springframework.security.core.context.SecurityContextHolder;
27  import org.springframework.security.oauth2.common.exceptions.BadClientCredentialsException;
28  import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
29  import org.springframework.security.web.AuthenticationEntryPoint;
30  import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
31  import org.springframework.security.web.authentication.AuthenticationFailureHandler;
32  import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
33  import org.springframework.security.web.util.matcher.RequestMatcher;
34  import org.springframework.web.HttpRequestMethodNotSupportedException;
35  
36  /**
37   * A filter and authentication endpoint for the OAuth2 Token Endpoint. Allows clients to authenticate using request
38   * parameters if included as a security filter, as permitted by the specification (but not recommended). It is
39   * recommended by the specification that you permit HTTP basic authentication for clients, and not use this filter at
40   * all.
41   * 
42   * @author Dave Syer
43   * 
44   */
45  public class ClientCredentialsTokenEndpointFilter extends AbstractAuthenticationProcessingFilter {
46  
47  	private AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
48  
49  	private boolean allowOnlyPost = false;
50  
51  	public ClientCredentialsTokenEndpointFilter() {
52  		this("/oauth/token");
53  	}
54  
55  	public ClientCredentialsTokenEndpointFilter(String path) {
56  		super(path);
57  		setRequiresAuthenticationRequestMatcher(new ClientCredentialsRequestMatcher(path));
58  		// If authentication fails the type is "Form"
59  		((OAuth2AuthenticationEntryPoint) authenticationEntryPoint).setTypeName("Form");
60  	}
61  
62  	public void setAllowOnlyPost(boolean allowOnlyPost) {
63  		this.allowOnlyPost = allowOnlyPost;
64  	}
65  
66  	/**
67  	 * @param authenticationEntryPoint the authentication entry point to set
68  	 */
69  	public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) {
70  		this.authenticationEntryPoint = authenticationEntryPoint;
71  	}
72  
73  	@Override
74  	public void afterPropertiesSet() {
75  		super.afterPropertiesSet();
76  		setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
77  			public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
78  					AuthenticationException exception) throws IOException, ServletException {
79  				if (exception instanceof BadCredentialsException) {
80  					exception = new BadCredentialsException(exception.getMessage(), new BadClientCredentialsException());
81  				}
82  				authenticationEntryPoint.commence(request, response, exception);
83  			}
84  		});
85  		setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
86  			public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
87  					Authentication authentication) throws IOException, ServletException {
88  				// no-op - just allow filter chain to continue to token endpoint
89  			}
90  		});
91  	}
92  
93  	@Override
94  	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
95  			throws AuthenticationException, IOException, ServletException {
96  
97  		if (allowOnlyPost && !"POST".equalsIgnoreCase(request.getMethod())) {
98  			throw new HttpRequestMethodNotSupportedException(request.getMethod(), new String[] { "POST" });
99  		}
100 
101 		String clientId = request.getParameter("client_id");
102 		String clientSecret = request.getParameter("client_secret");
103 
104 		// If the request is already authenticated we can assume that this
105 		// filter is not needed
106 		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
107 		if (authentication != null && authentication.isAuthenticated()) {
108 			return authentication;
109 		}
110 
111 		if (clientId == null) {
112 			throw new BadCredentialsException("No client credentials presented");
113 		}
114 
115 		if (clientSecret == null) {
116 			clientSecret = "";
117 		}
118 
119 		clientId = clientId.trim();
120 		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(clientId,
121 				clientSecret);
122 
123 		return this.getAuthenticationManager().authenticate(authRequest);
124 
125 	}
126 
127 	@Override
128 	protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
129 			FilterChain chain, Authentication authResult) throws IOException, ServletException {
130 		super.successfulAuthentication(request, response, chain, authResult);
131 		chain.doFilter(request, response);
132 	}
133 
134 	protected static class ClientCredentialsRequestMatcher implements RequestMatcher {
135 
136 		private String path;
137 
138 		public ClientCredentialsRequestMatcher(String path) {
139 			this.path = path;
140 
141 		}
142 
143 		@Override
144 		public boolean matches(HttpServletRequest request) {
145 			String uri = request.getRequestURI();
146 			int pathParamIndex = uri.indexOf(';');
147 
148 			if (pathParamIndex > 0) {
149 				// strip everything after the first semi-colon
150 				uri = uri.substring(0, pathParamIndex);
151 			}
152 
153 			String clientId = request.getParameter("client_id");
154 
155 			if (clientId == null) {
156 				// Give basic auth a chance to work instead (it's preferred anyway)
157 				return false;
158 			}
159 
160 			if ("".equals(request.getContextPath())) {
161 				return uri.endsWith(path);
162 			}
163 
164 			return uri.endsWith(request.getContextPath() + path);
165 		}
166 
167 	}
168 
169 }