首页 > 学院 > 开发设计 > 正文

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

2019-11-15 00:46:38
字体:
来源:转载
供稿:网友
使用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-urlpattern="/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或者其它存放客户端信息的地方获取。示例代码可以查看图1MyClientDetailsService示例及token获取。注意,这里的getAuthorities()方法对配的access="ROLE_ABCS"没有影响。图1MyClientDetailsService示例及token获取?五、经过以上的步骤,我们就可以进行RESTful服务发布了,发布成功后,需要进行用户认证的试验。1、如图1MyClientDetailsService示例及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值请求返回的结果这就说明,我们的用户认证配置达到了预期效果。

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

来自王安琪


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表