使用Spring Security Oauth2完成RESTful服务password认证的过程

          摘要:Spring Security与Oauth2整合步骤中详细描述了使用过程 ,但它对于入门者有些重量级,比如将用户信息、ClientDetails、token存入数据库而非内存 。配置过程比较复杂,经过几天时间试验终于成功,下面我将具体的使用 Spring Security Oauth2完成password认证的过程记录下来与大家分享。
        关键字: HTTP Authentication, rest, spring security , spring mvc
        前提:IntelliJ IDEA ( 13.1.5 版本),  apache maven  3.2.3 版本),  Tomcat(7.0.56版本), Spring(3.2.4版本),  spring-security-oauth2(2.0.7 版本

 
 
一、首先需要使用Spring MVC完成RESTful API的发布,这一步骤的详细情况可见我的另一博文:应用Spring MVC 发布restful服务是怎样的一种体验
二、在/webapp/WEB-INF/web.xml文件中添加相应的filter: org.springframework.web.filter.DelegatingFilterProxy mapping ,具体如以下所示。
<?xml version= "1.0"  encoding= "UTF-8" ?>
<web-app xmlns= "http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation= "http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version= "3.1" >
    <filter>
        <filter- name >springSecurityFilterChain</filter- name >
        <filter- class >org.springframework.web.filter.DelegatingFilterProxy</filter- class >
    </filter>
    <filter-mapping>
        <filter- name >springSecurityFilterChain</filter- name >
        < url -pattern>/*</ url -pattern>
    </filter-mapping>

    <context- param >
        < param - name >contextConfigLocation</ param - name >
        < param - value >
            /WEB-INF/security.xml
        </ param - value >
    </context- param >
    <listener>
        <listener- class >org.springframework.web.context.ContextLoaderListener</listener- class >
    </listener>

    <servlet>
        <servlet- name >restful</servlet- name >
        <servlet- class >org.springframework.web.servlet.DispatcherServlet</servlet- class >
        <load-on-startup> 1 </load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet- name >restful</servlet- name >
        < url -pattern>/</ url -pattern>
    </servlet-mapping>
</web-app>
三、在上一步的web.xml文件中可以看到,Spring需要加载 /WEB-INF/下的security.xml文件,因此我们在 /WEB-INF/下创建 security.xml文件,其主要内容如下所示。这里比 使用Spring Security完成RESTful服务用户认证中的 security.xml文件配置要复杂得多(见我之前的博文: 使用Spring Security完成RESTful服务用户认证的过程 )。注意,这里的配置文件中的  <security:http pattern= "/abcs/**" <security:intercept- url  pattern= "/abcs/**"  access= "ROLE_ABCS" />  access必须指定一个角色名称,使用  use-expressions= "true"  isAuthenticated() 这里是不允许的。因此,我们需要在 实现UserDetails接口、实现 getAuthorities()方法时返回 此角色名称,在拥有此角色的用户认证通过后,才可以访问/abcs/**资源。
<?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:oauth2= "http://www.springframework.org/schema/security/oauth2"
       xmlns:mvc= "http://www.springframework.org/schema/mvc"
       xmlns:security= "http://www.springframework.org/schema/security"
       xsi:schemaLocation= "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/security/oauth2  http://www.springframework.org/schema/security/spring-security-oauth2.xsd" >

    <mvc:annotation-driven/>
    <mvc:default-servlet-handler/>

    <bean id = "tokenStore" class = "org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />
    <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="clientDetailsService"/>-->
    </bean>
    <bean id = "clinetAuthenticationEntryPoint"
          class = "org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />
    <bean id = "accessDeniedHandler"
          class = "org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
    <bean id = "userApprovalHandler"
          class = "org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler" />

    <!--client-->
    <bean id = "clientDetailsService" class = "com.anqi.dp.controllers.MyClientDetailsService" />
    <bean id = "clientDetailsUserDetailsService"
          class = "org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService" >
        <constructor-arg ref= "clientDetailsService" />
    </bean>
    <bean id = "clientCredentialsTokenEndpointFilter"
          class = "org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter" >
        <property name = "authenticationManager" ref= "clientAuthenticationManager" />
    </bean>
    <security:authentication-manager id = "clientAuthenticationManager" >
        <security:authentication-provider user-service-ref= "clientDetailsUserDetailsService" />
    </security:authentication-manager>
    <oauth2:authorization-server client-details-service-ref= "clientDetailsService" token-services-ref= "tokenServices"
                                 user-approval-handler-ref= "userApprovalHandler" >
        <oauth2:authorization- code />
        <oauth2:implicit/>
        <oauth2:refresh-token/>
        <oauth2:client-credentials/>
        <oauth2:password/>
    </oauth2:authorization-server>
    <security:http pattern= "/oauth/token" create-session= "stateless"
                   authentication-manager-ref= "clientAuthenticationManager" >
        <security:anonymous enabled = "false" />
        <security:http-basic entry-point-ref= "clinetAuthenticationEntryPoint" />
        <security:custom-filter ref= "clientCredentialsTokenEndpointFilter" before= "BASIC_AUTH_FILTER" />
        <security:access-denied-handler ref= "accessDeniedHandler" />
    </security:http>
     <!--client-->
     <!--user-->
    <bean id = "userService" class = "com.anqi.dp.controllers.UserService" />
    <security:authentication-manager alias= "authenticationManager" >
        <security:authentication-provider user-service-ref= "userService" >
            <!--<security:password-encoder hash="md5"/>-->
        </security:authentication-provider>
    </security:authentication-manager>
     <!--user-->

    <oauth2:resource-server id = "mobileResourceServer" resource- id = "mobile-resource" token-services-ref= "tokenServices" />
    <bean id = "accessDecisionManager" class = "org.springframework.security.access.vote.UnanimousBased" >
        <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>
    <security:http pattern= "/abcs/**" create-session= "never" entry-point-ref= "clinetAuthenticationEntryPoint"
                   access-decision-manager-ref= "accessDecisionManager" >
        <security:anonymous enabled = "false" />
        <security:intercept- url pattern= "/abcs/**" access= "ROLE_ABCS" />
        <security:custom-filter ref= "mobileResourceServer" before= "PRE_AUTH_FILTER" />
        <security:access-denied-handler ref= "accessDeniedHandler" />
    </security:http>

</beans>
四、当然要新建com.anqi.dp.UserService类了,其在 security.xml文件中配置其为 authenticationManager authentication-provider。 com.anqi.dp.UserService类实现自UserDetailsService接口,它需要实现一loadUserByUsername方法,在实现此方法的过程中,又需要新建MyUserDetails类来实现UserDetails接口。实现 loadUserByUsername方法时,可以自己依需要从关系数据库、NoSQL 或者其它存放用户信息的地方获取。示例代码可以查看 之前的博文: 使用Spring Security完成RESTful服务用户认证的过程  图1 UserService示例及用户名密码登陆 。注意,因为配了 access= "ROLE_ABCS" ,因此需要   在拥有相应角色的用户 getAuthorities()方法内返回 此角色名称: SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_ABCS"); 。
还要新建com.anqi.dp.MyClientDetailsService类,其 security.xml文件中配置其为 clientAuthenticationManager authentication-provider。 com.anqi.dp.MyClientDetailsService类实现自 ClientDetailsService接口 ,它需要实现一loadClientByClientId方法,在实现此方法的过程中,又需要新建MyClientDetails类来实现ClientDetails接口。实现 loadClientByClientId 方法时,可以自己依需要从关系数据库、NoSQL 或者其它存放客户端信息的地方获取。示例代码可以查看 图1  MyClientDetailsService 示例及token获取。注意, 这里的 getAuthorities()方法对 配的 access= "ROLE_ABCS"  没有影响。
 
  图1  MyClientDetailsService 示例及token获取
五、经过以上的步骤,我们就可以进行RESTful服务发布了,发布成功后,需要进行用户认证的试验。
1、如图1  MyClientDetailsService 示例及token获取 所示,我们使用REST Client工具对  http://127.0.0.1:8088/restfulservice/oauth/token  路径发出POST请求,其中需要在Request Parameters中添加client_id、client_secret、grant_type与user_name、password键值对。 如此, 即进行了模拟的通过用户名密码获取token的过程 。图2中是client认证失败时的Response,我将client_secret更改后的结果。图3是user认证失败时的 Response,我将password更改后的结果。 图4是认证成功时的 Response。可以看出,在认证成功时的 Response中存在access_token字段,这就是我们获取到的token
 
 
图2 client认证失败
 
图3 user认证失败
 
 
 
图4 认证成功
 
 
2、我们在认证成功的条件下,使用上面步骤中返回的 access_token   http://localhost:8088/restfulservice/abcs/6?access_token=a7f3e13e-cbb0-417d-a9f8-9764d11db00f   进行 GET 请求,即可以成功得到返回结果, 我是使用浏览器进行HTTP请求的 。(具体的逻辑使用Spring MVC的Control完成,见我之前的博文:)。在调试时,可以看到每次请求,进入对应Controller后,代码均会转入 UserDetails的String getUsername()方法中。
如果对请求路径里的 access_token值 稍作修改,如再   http://localhost:8088/restfulservice/abcs/6?access_token=a7f3e13e-cbb0-417d-a9f8-9764d11db008   进行 GET 请求,则返回不到正确结果,如图5所示,即返回Invalid access token错误。
 
图5  带正确的access_token值请求返回的结果
 
 
 
图6 带不正确的access_token值请求返回的结果
 
 
如果删除access_token,不带 access_token值对 http://localhost:8088/restfulservice/abcs/6  进行GET请求时,返回的错误信息如图7所示。
 
图7 不带 access_token值请求返回的结果
 
 
这就说明,我们的用户认证配置达到了预期效果。
 

最近有各种之前没有碰到过的问题、技术,有时间整理好分享给大家。



转载于:https://www.cnblogs.com/wgp13x/p/4626026.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值