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重新发起验证请求
<?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
|
资源请求:
补充: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