* 使用者注冊和登入
* Email驗證
* Password 丢失
采取的技術有以下:
* OAuth2 Protocol
* Spring Integration
* Spring Data
* Jersey/JAX-RS
* Gradle / Groovy
通過以下方式建構項目:
> cd oauth2-provider
> ./gradlew clean build integrationTest
運作Web項目:
這個應用是基于MongoDB作為持久層,在運作應用之前确認mongod是運作在端口27017.
運作指令:
> ./gradlew tomcatRun
1. 建立一個使用者:
curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
-d '{"user":{"emailAddress":"[email protected]"}, "password":"password"}' \
'http://localhost:8080/oauth2-provider/v1.0/users'
結果應該是:
{"apiUser":
{"emailAddress":"[email protected]",
"firstName":null,
"lastName":null,
"age":null,
"id":"8a34d009-3558-4c8c-a8da-1ad2b2a393c7",
"name":"[email protected]"},
"oauth2AccessToken":
{"access_token":"7e0e4708-7837-4a7e-9f87-81c6429b02ac",
"token_type":"bearer",
"refresh_token":"d0f248ab-e30f-4a85-860c-bd1e388a39b5",
"expires_in":5183999,
"scope":"read write"
}
}
2. 請求一個access token:
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=password&[email protected]&password=password'
{
"access_token":"a838780e-35ef-4bd5-92c0-07a45aa74948",
"token_type":"bearer",
"refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
"expires_in":5183999
3. 重新整理一個token:
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=refresh_token&refresh_token=ab06022f-247c-450a-a11e-2ffab116e3dc'
"access_token":"4835cd11-8bb7-4b76-b857-55c6e7f36fc4",
"token_type":"bearer",
"refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
"expires_in":5183999
一個Jersey 處理所有資源調用:
<servlet-mapping>
<servlet-name>jersey-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Spring servlet處理所有oauth 調用:
<servlet-name>spring</servlet-name>
<url-pattern>/oauth/*</url-pattern>
spring security配合定義一個過濾器:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>contextAttribute</param-name>
<param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value>
</init-param>
</filter>
對根目錄下所有url進行 過濾:
<filter-mapping>
<url-pattern>/*</url-pattern>
</filter-mapping>
<oauth:authorization-server client-details-service-ref="client-details-service" token-services-ref="tokenServices">
<oauth:refresh-token/>
<oauth:password/>
</oauth:authorization-server>
預設的token端點是/oauth/token ,隻有 password flow 和重新整理 token 支援。
使用Spring security 保護token端點:
<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false"/>
<http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
<access-denied-handler ref="oauthAccessDeniedHandler"/>
</http>
下面配置授權authentication 管理器和用戶端服務:
<bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="clientAuthenticationManager"/>
</bean>
<authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="client-details-user-service"/>
</authentication-manager>
<bean id="client-details-user-service" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="client-details-service" />
Resource Owner Password flow 需要管理使用者的授權管理器
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>
<sec:authentication-manager alias="userAuthenticationManager">
<sec:authentication-provider user-service-ref="userService">
<sec:password-encoder ref="passwordEncoder"/>
</sec:authentication-provider>
</sec:authentication-manager>
密碼 password encoder是用于加密密碼。使用者服務必須實作一個UserDetailsService ,能根據使用者名傳回使用者。
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
notNull(username, "Mandatory argument 'username' missing.");
User user = userRepository.findByEmailAddress(username.toLowerCase());
if (user == null) {
throw new AuthenticationException();
}
return user;
}
<bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore"/>
<property name="supportRefreshToken" value="true"/>
<property name="clientDetailsService" ref="client-details-service"/>
<oauth:resource-server id="resourceServerFilter" token-services-ref="tokenServices"/>
這個服務提供基于通路token獲得使用者的資訊。URL格式:
/v1.0/users/{id}/someresource
@Path("/v1.0/me")
@Component
@Produces({MediaType.APPLICATION_JSON})
@Consumes({MediaType.APPLICATION_JSON})
public class MeResource extends BaseResource {
@RolesAllowed({"ROLE_USER"})
@GET
public ApiUser getUser(final @Context SecurityContext securityContext) {
User requestingUser = loadUserFromSecurityContext(securityContext);
if(requestingUser == null) {
throw new UserNotFoundException();
}
return new ApiUser(requestingUser);
protected User loadUserFromSecurityContext(SecurityContext securityContext) {
OAuth2Authentication requestingUser = (OAuth2Authentication) securityContext.getUserPrincipal();
Object principal = requestingUser.getUserAuthentication().getPrincipal();
User user = null;
if(principal instanceof User) {
user = (User)principal;
} else {
user = userRepository.findByEmailAddress((String)principal);
return user;
測試:
curl -v -X GET \
-H "Content-Type: application/json" \
-H "Authorization: Bearer [your token here]" \
'http://localhost:8080/oauth2-provider/v1.0/me'
參考:https://github.com/tcompiegne/couchbase-token-store-spring-oauth2
https://github.com/tcompiegne/oauth2-server-spring-couchbase
轉自:http://www.jdon.com/dl/best/securing-rest-services-with-spring.html.html