spring-oauth2 (bearer)是基于spring-security的验证机制,对于第三方访问受限资源时通过token机制来验证
验证steps:
通过时序图来看一下,验证方式:
发送username, password, client_id, client_secret, grant_type到server
server返回包括access_token, token_type, refresh_token, expires_in
其中,expires_in有效期,如果超期了,refresh_token起作用,如下:
使用refresh_token重新发起验证请求
来看一下,如何通过spring配置,完成上面的验证机制:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oauth="http://www.springframework.org/schema/security/oauth2" xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <sec:http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="authenticationManager"> <sec:intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" /> <sec:anonymous enabled="false" /> <sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" /> <sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" /> <sec:access-denied-handler ref="oauthAccessDeniedHandler" /> </sec:http> <sec:http pattern="/protected/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"> <sec:anonymous enabled="false" /> <sec:intercept-url pattern="/protected/**" method="GET" access="IS_AUTHENTICATED_FULLY" /> <sec:custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" /> <sec:access-denied-handler ref="oauthAccessDeniedHandler" /> </sec:http> <bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> </bean> <bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"> <property name="realmName" value="springsec/client" /> <property name="typeName" value="Basic" /> </bean> <bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"> </bean> <bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"> <property name="authenticationManager" ref="authenticationManager" /> </bean> <sec:authentication-manager alias="authenticationManager"> <sec:authentication-provider user-service-ref="clientDetailsUserService" /> </sec:authentication-manager> <bean id="clientDetailsUserService" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"> <constructor-arg ref="clientDetails" /> </bean> <bean id="clientDetails" class="it.iol.oauthaaa.security.AAAGuestServiceImpl"> <property name="id" value="mysupplycompany" /> <property name="secretKey" value="mycompanykey" /> </bean> <sec:authentication-manager id="userAuthenticationManager"> <sec:authentication-provider ref="customUserAuthenticationProvider" /> </sec:authentication-manager> <bean id="customUserAuthenticationProvider" class="it.iol.oauthaaa.security.AAAUserAuthenticationProvider"> </bean> <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"> <oauth:authorization-code /> <oauth:implicit/> <oauth:refresh-token/> <oauth:client-credentials /> <oauth:password authentication-manager-ref="userAuthenticationManager"/> </oauth:authorization-server> <oauth:resource-server id="resourceServerFilter" resource-id="springsec" token-services-ref="tokenServices" /> <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" /> <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices"> <property name="tokenStore" ref="tokenStore" /> <property name="supportRefreshToken" value="true" /> <property name="accessTokenValiditySeconds" value="120"></property> <property name="clientDetailsService" ref="clientDetails" /> </bean> <mvc:annotation-driven /> <mvc:default-servlet-handler /> <context:annotation-config/> <bean id="MyResource" class="it.iol.oauthaaa.resources.UserResource"></bean> <bean id="aaaProxy" class="it.iol.oauthaaa.security.AAAProxy"></bean </beans>
通过发起/oauth/token 请求, filter拦截处理,具体代码:
@Service
public class AAAGuestServiceImpl implements ClientDetailsService {
private String id;
private String secretKey;
@Override
public ClientDetails loadClientByClientId(String clientId)
throws OAuth2Exception {
if (clientId.equals(id))
{
List<String> authorizedGrantTypes = new ArrayList<String>();
authorizedGrantTypes.add("password");
authorizedGrantTypes.add("refresh_token");
authorizedGrantTypes.add("client_credentials");
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setClientId(id);
clientDetails.setClientSecret(secretKey);
clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);
return clientDetails;
}
else {
throw new NoSuchClientException("No client recognized with id: "
+ clientId);
}
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
}
现在基于username和passwd来验证(grant_type=password), 在<oauth:authorization-server>的拦截userAuthenticationManager中:
public class AAAUserAuthenticationProvider
implements AuthenticationProvider {
@Autowired
AAAProxy aaaProxy;
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
boolean result = aaaProxy.isValidUser(authentication.getPrincipal()
.toString(), authentication.getCredentials().toString());
if (result) {
List<GrantedAuthority> grantedAuthorities =
new ArrayList<GrantedAuthority>();
AAAUserAuthenticationToken auth =
new AAAUserAuthenticationToken(authentication.getPrincipal(),
authentication.getCredentials(), grantedAuthorities);
return auth;
} else {
throw new BadCredentialsException("Bad User Credentials.");
}
}
public boolean supports(Class<?> arg0) {
return true;
}
}
这里的aaproxy是真正来验证user的(代理机制)不细化了,如果用户是有效,做三件事:
1. 在框架的security上下文中更新对象
2. TokenService将会生成一个新的token
3. TokenStore将会保存这个token
然后token就发给request的client了
最后这里是token和resource的代码:
public class AAAUserAuthenticationToken
extends AbstractAuthenticationToken {
private static final long serialVersionUID = -1092219614309982278L;
private final Object principal;
private Object credentials;
public AAAUserAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
}
@Path("/userresource")
public class UserResource {
@GET
@Path("/userprofile")
public String getUserProfile(){
return "Welcome in protected Area. User enabled.";
}
}
测试:
使用了Fidder, 可以用curl,wget(what ever you want)
请求:
/OAuthAAA/oauth/token
?username=myuser&password=mypassword
&client_id=mysupplycompany&client_secret=mycompanykey&grant_type=password
|
资源请求:
/OAuthAAA/protected/userresource/userprofile
Authorization: Bearer 5cf0732b-6bbb-40c7-8fab-dcfefcc2fcfe
![]() |
补充:AAProxy的代码:
package it.iol.oauthaaa.security;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.util.List;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class AAAProxy {
private Proxy proxy;
private RestTemplate template;
public AAAProxy() {
proxy = new Proxy(Type.HTTP, new InetSocketAddress(
"proxy.abc.net", 3001));
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setProxy(proxy);
template = new RestTemplate(requestFactory);
}
public boolean isValidUser(String user, String password) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("user", user);
map.add("password", password);
HttpEntity<String> response = template.postForEntity(
"https://authentication.local/auth", map,
String.class);
HttpHeaders headers = response.getHeaders();
List<String> cookies = headers.get("Set-Cookie");
for (String cookie : cookies) {
if (cookie.indexOf("Auth")!=-1)
return true;
}
return false;
}
}
当grant_type="client_credentials"时的验证:
相关资源:
https://raymondhlee.wordpress.com/2014/12/21/implementing-oauth2-with-spring-security/
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/main/java/org/springframework/security/oauth2/provider/authentication/BearerTokenExtractor.java
https://github.com/spring-projects/spring-security-oauth/tree/master/spring-security-oauth2
https://developer.linkedin.com/docs/oauth2
http://api-doc.assembla.com/content/authentication.html