RESTful Web服务+ Spring Security:具有访问令牌的API服务?

时间:2015-07-30 07:12:05

标签: json spring rest oauth spring-security

我已经在我的restful web服务中实现了spring security。

访问令牌的OAuth请求

http://localhost:8084/Weekenter/oauth/token?grant_type=password&client_id=restapp&client_secret=restapp&username=anoo@codelynks.com&password=mypass

作为回应,我将获得访问令牌和刷新令牌。

现在,我可以通过以下查询字符串参数方法请求具有给定访问令牌的API服务。并且工作正常。

请求格式

enter image description here

  

在上面的快照中,我有JSON格式的请求,access_token除外。

{
"mainCategory":"Deals",
"subCategory":"Vigo"
}
  

事实上,我必须采用以下格式。

{
"mainCategory":"Deals",
"subCategory":"Vigo",
"access_token":"122356545555"
}
  

但是,当我尝试这个时,它会抛出错误

<oauth> 
<error_description>
An Authentication object was not found in the SecurityContext
</error_description>
<error>
unauthorized
</error>
</oauth>
  

弹簧security.xml文件

<?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:context="http://www.springframework.org/schema/context"
       xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd 
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd ">



    <!-- This is default url to get a token from OAuth -->
    <http pattern="/oauth/token" create-session="stateless"
          authentication-manager-ref="clientAuthenticationManager"
          xmlns="http://www.springframework.org/schema/security">
        <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" />
        <anonymous enabled="false" />
        <http-basic entry-point-ref="clientAuthenticationEntryPoint" />
        <!-- include this only if you need to authenticate clients via request 
        parameters -->
        <custom-filter ref="clientCredentialsTokenEndpointFilter" 
                       after="BASIC_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>

    <!-- This is where we tells spring security what URL should be protected 
    and what roles have access to them -->
    <!--    <http pattern="/api/**" create-session="never"
          entry-point-ref="oauthAuthenticationEntryPoint"
          access-decision-manager-ref="accessDecisionManager"
          xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false" />
        <intercept-url pattern="/api/**" access="ROLE_APP" />
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>-->


    <http pattern="/utililty/**" create-session="never"
          entry-point-ref="oauthAuthenticationEntryPoint"
          access-decision-manager-ref="accessDecisionManager"
          xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false" />
        <intercept-url pattern="/utililty/getNationalityList" access="ROLE_APP" />
        <intercept-url pattern="/utililty/getSettingsUrl" access="ROLE_APP" />
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>


    <http pattern="/admin/**" create-session="never"
          entry-point-ref="oauthAuthenticationEntryPoint"
          access-decision-manager-ref="accessDecisionManager"
          xmlns="http://www.springframework.org/schema/security">
        <anonymous enabled="false" />
        <intercept-url pattern="/admin/fetch-sub-category" access="ROLE_APP" />
        <intercept-url pattern="/admin/create-category" access="ROLE_APP" />
        <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
        <access-denied-handler ref="oauthAccessDeniedHandler" />
    </http>


    <bean id="oauthAuthenticationEntryPoint"
          class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
        <property name="realmName" value="test" />
    </bean>

    <bean id="clientAuthenticationEntryPoint"
          class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
        <property name="realmName" value="test/client" />
        <property name="typeName" value="Basic" />
    </bean>

    <bean id="oauthAccessDeniedHandler"
          class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />

    <bean id="clientCredentialsTokenEndpointFilter"
          class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
        <property name="authenticationManager" ref="clientAuthenticationManager" />
    </bean>

    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"
          xmlns="http://www.springframework.org/schema/beans">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter" />
                <bean class="org.springframework.security.access.vote.RoleVoter" />
                <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
            </list>
        </constructor-arg>
    </bean>


    <authentication-manager id="clientAuthenticationManager"
                            xmlns="http://www.springframework.org/schema/security">
        <authentication-provider user-service-ref="clientDetailsUserService" />
    </authentication-manager>


    <!-- Custom User details service which is provide the user data -->
    <bean id="customUserDetailsService"
          class="com.weekenter.www.service.impl.CustomUserDetailsService" />

    <!-- Authentication manager -->
    <authentication-manager alias="authenticationManager"
                            xmlns="http://www.springframework.org/schema/security">
        <authentication-provider user-service-ref="customUserDetailsService">  
            <password-encoder hash="plaintext">  
            </password-encoder>
        </authentication-provider> 
    </authentication-manager>




    <bean id="clientDetailsUserService"
          class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
        <constructor-arg ref="clientDetails" />
    </bean>


    <!-- This defined token store, we have used inmemory tokenstore for now 
    but this can be changed to a user defined one -->
    <bean id="tokenStore"
          class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />

    <!-- This is where we defined token based configurations, token validity 
    and other things -->
    <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 name="clientDetailsService" ref="clientDetails" />
    </bean>

    <bean id="userApprovalHandler"
          class="org.springframework.security.oauth2.provider.approval.TokenServicesUserApprovalHandler">
        <property name="tokenServices" ref="tokenServices" />
    </bean>

    <oauth:authorization-server
        client-details-service-ref="clientDetails" token-services-ref="tokenServices"
        user-approval-handler-ref="userApprovalHandler">
        <oauth:authorization-code />
        <oauth:implicit />
        <oauth:refresh-token />
        <oauth:client-credentials />
        <oauth:password />
    </oauth:authorization-server>

    <oauth:resource-server id="resourceServerFilter"
                           resource-id="test" token-services-ref="tokenServices" />

    <oauth:client-details-service id="clientDetails">
        <!-- client -->
        <oauth:client client-id="restapp"
                      authorized-grant-types="authorization_code,client_credentials"
                      authorities="ROLE_APP" scope="read,write,trust" secret="secret" />

        <oauth:client client-id="restapp"
                      authorized-grant-types="password,authorization_code,refresh_token,implicit"
                      secret="restapp" authorities="ROLE_APP" />

    </oauth:client-details-service>

    <sec:global-method-security
        pre-post-annotations="enabled" proxy-target-class="true">
        <!--you could also wire in the expression handler up at the layer of the 
        http filters. See https://jira.springsource.org/browse/SEC-1452 -->
        <sec:expression-handler ref="oauthExpressionHandler" />
    </sec:global-method-security>

    <oauth:expression-handler id="oauthExpressionHandler" />
    <oauth:web-expression-handler id="oauthWebExpressionHandler" />
</beans>
  

自定义身份验证处理程序

@Service
@Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private LoginDao loginDao;

    public UserDetails loadUserByUsername(String login)
            throws UsernameNotFoundException {

        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        com.weekenter.www.entity.User user = null;
        try {
            user = loginDao.getUser(login);
            if (user != null) {
                if (user.getStatus().equals("1")) {
                    enabled = false;
                }
            } else {
                throw new UsernameNotFoundException(login + " Not found !");
            }

        } catch (Exception ex) {
            try {
                throw new Exception(ex.getMessage());
            } catch (Exception ex1) {
            }
        }

        return new User(
                user.getEmail(),
                user.getPassword(),
                enabled,
                accountNonExpired,
                credentialsNonExpired,
                accountNonLocked,
                getAuthorities()
        );
    }

    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authList = getGrantedAuthorities(getRoles());
        return authList;
    }

    public List<String> getRoles() {
        List<String> roles = new ArrayList<String>();
        roles.add("ROLE_APP");
        return roles;
    }

    public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        for (String role : roles) {
            authorities.add(new SimpleGrantedAuthority(role));
        }
        return authorities;
    }

1 个答案:

答案 0 :(得分:5)

为什么要使用请求正文传递访问令牌。你需要传递带标题的令牌。以下是处理访问和刷新令牌的3个步骤 -

1-获取访问令牌 -

http://localhost:8080/webapp/oauth/token?grant_type=password&client_id=abc&client_secret=xyz&username=user1&password=user1

此请求为您提供访问令牌和刷新令牌。

2-将此访问令牌作为标头传递并访问受保护资源

http://localhost:8080/webapp/calendar/doctor/search?cityid=1

部首:

授权= Bearer f8e5f8cc-9465-4789-8734-9fa97e9fe05a

3-如果先前的令牌过期,则获取新的访问令牌。

http://localhost:8080/webapp/oauth/token?grant_type=refresh_token&client_id=client1&client_secret=client1&refresh_token=323872a7-5d25-40a6-adc6-80e19c5c8cea

如下所示拥有身份验证提供程序 -

    import java.util.ArrayList;
    import java.util.List;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.authentication.AuthenticationProvider;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.GrantedAuthority;

  public class HealthcareUserAuthenticationProvider implements  AuthenticationProvider {

    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

            AppPasswordAuthenticationToken userPassAuthToken;

            List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
            String userName = (String) authentication.getPrincipal();

            if (authentication.getCredentials().equals(AppSecurityDAO.getDocUserPassword(userName))) {
                userPassAuthToken = new HealthcareUserPasswordAuthenticationToken(authentication.getPrincipal(),
                        authentication.getCredentials(), grantedAuthorities);
            } else if (authentication.getCredentials().equals(AppSecurityDAO.getUserPassword(userName))){
                userPassAuthToken = new AppUserPasswordAuthenticationToken(authentication.getPrincipal(),
                        authentication.getCredentials(), grantedAuthorities);
            }else {
                throw new BadCredentialsException("Bad User Credentials.");
            }
            return userPassAuthToken;
        }

拥有Cliend Detail服务,如下所示 -

@Service
public class HealthcareClientDetailsService implements ClientDetailsService {

    @Autowired
    AppSecurityDAO appSecurityDAO;

    public ClientDetails loadClientByClientId(String clientId) throws OAuth2Exception {

        String clientSecret = appSecurityDAO.getClientOauthSecret(clientId);

        if (clientId.equals("client1")) {

            List<String> authorizedGrantTypes = new ArrayList<String>();
            authorizedGrantTypes.add("password");
            authorizedGrantTypes.add("refresh_token");
            authorizedGrantTypes.add("client_credentials");

            BaseClientDetails clientDetails = new BaseClientDetails();
            clientDetails.setClientId(clientId);
            clientDetails.setClientSecret(clientSecret);
            clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);

            return clientDetails;

        } else if (clientId.equals("client2")) {

            List<String> authorizedGrantTypes = new ArrayList<String>();
            authorizedGrantTypes.add("password");
            authorizedGrantTypes.add("refresh_token");
            authorizedGrantTypes.add("client_credentials");

            BaseClientDetails clientDetails = new BaseClientDetails();
            clientDetails.setClientId("client2");
            clientDetails.setClientSecret("client2");
            clientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);

            return clientDetails;
        }

        else {
            throw new NoSuchClientException("No client with requested id: " + clientId);
        }
    }

并拥有自己的自定义凭据验证,即AppUserPasswordAuthenticationToken,如下所示 -

public class AppUserPasswordAuthenticationToken extends AbstractAuthenticationToken { --
    private static final long serialVersionUID = 310L;
    private final Object principal;
    private Object credentials;
    private int loginId;

    public int getLoginId() {
        return loginId;
    }
    public void setLoginId(int loginId) {
        this.loginId = loginId;
    }
    public AppUserPasswordAuthenticationToken(Object principal, Object credentials) { --
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }
    public AppUserPasswordAuthenticationToken(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;
    }
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted");
        }

        super.setAuthenticated(false);
    }
    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }
}