Spirng Security OAuth 开发指南-2

  上一章解释了OAuth的基本原理,这章是怎么去实现Security OAuth服务 ;

  本章的Oauth参考开源项目:    https://gitee.com/shengzhao/spring-oauth-server  配置解析,详细的可以参考这个项目。

 

OAuth2 的授权方式:

1. authorization_code — 授权码模式(即先登录获取code,再获取token)

2. password — 密码模式(将用户名,密码传过去,直接获取token)

3. refresh_token — 刷新access_token

4. implicit — 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)

5. client_credentials — 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向'服务端'获取资源)

最常用的模式:

【授权码模式(Authorization Code)】

具体实现的过程:

security.xml 文件:

   oauth2是security的一部分,配置也有关联,

   启用用注解; TokenEndpoint与AuthorizationEndpoint需要

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

  TokenServices 配置:

  • TokenStore, 使用JdbcTokenStore, 将token信息存放数据库, 需要提供一个dataSource对象; 也可使用InMemoryTokenStore存于内存中

 <!--Config token services-->
 <!--<beans:bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore"/>-->
 <beans:bean id="tokenStore" class="com.monkeyk.sos.domain.oauth.CustomJdbcTokenStore">
    <beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>

    注: 可以在spring-security-oauth2中找到对应的SQL脚本, 地址为https://github.com/spring-projects/spring-security-oauth/tree/master/spring-security-oauth2/src/test/resources, 目录中的schema.sql 即是. (以下不再说明SQL脚本的问题)

  • TokenServices; 需要注入TokenStore

<beans:bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
        <beans:property name="tokenStore" ref="tokenStore"/>
        <beans:property name="clientDetailsService" ref="clientDetailsService"/>
        <beans:property name="supportRefreshToken" value="true"/>
 </beans:bean>

    如果允许刷新token 请将supportRefreshToken 的值设置为true, 默认为不允许

  •  ClientDetailsService 配置, 使用JdbcClientDetailsService, 也需要提供dataSource, 替换demo中直接配置在配置文件中,自定义了CustomJdbcClientDetailsService 继承JdbcClientDetailsService:

<beans:bean id="clientDetailsService" class="com.monkeyk.sos.domain.oauth.CustomJdbcClientDetailsService">
  <beans:constructor-arg index="0" ref="dataSource"/>
</beans:bean>
  • ClientDetailsUserDetailsService配置, 该类实现了Spring security中 UserDetailsService 接口

 <beans:bean id="oauth2ClientDetailsUserService"
                class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
        <beans:constructor-arg ref="clientDetailsService"/>
    </beans:bean>
  • OAuth2AuthenticationEntryPoint配置

<beans:bean id="oauth2AuthenticationEntryPoint"
                class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint"/>
  • oauth2 AuthenticationManager配置; 在整个配置中,有两个AuthenticationManager需要配置

<authentication-manager id="oauth2AuthenticationManager">
        <authentication-provider user-service-ref="oauth2ClientDetailsUserService"/>
    </authentication-manager>

  • 第二个AuthenticationManager用于向获取UserDetails信息, 

<authentication-manager alias="authenticationManager">
        <authentication-provider user-service-ref="userService">
            <!--

            密码使用 MD5 加密 ; 实际使用时推荐使用 SHA-256, 并使用 salt

            -->
            <password-encoder hash="md5"/>
        </authentication-provider>
    </authentication-manager>

 userService是一个实现UserDetailsService的Bean

  • OAuth2AccessDeniedHandler配置, 实现AccessDeniedHandler接口

 <beans:bean id="oauth2AccessDeniedHandler"
                class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
  •  UserApprovalHandler配置, 这儿使用DefaultUserApprovalHandler, 这里是实现client是否可信任的关键点,你可以扩展该接口来自定义approval行为

<beans:bean id="oauthUserApprovalHandler" class="com.monkeyk.sos.web.oauth.OauthUserApprovalHandler">
        <beans:property name="tokenStore" ref="tokenStore"/>
        <beans:property name="clientDetailsService" ref="clientDetailsService"/>
        <beans:property name="requestFactory" ref="oAuth2RequestFactory"/>
        <beans:property name="oauthService" ref="oauthService"/>
    </beans:bean>

  • authorization-server配置, 核心

    <!--

        Security OAuth Flow的核心配置

        每一个配置对应一类具体的grant_type

        可根据需求删除或禁用, 如: <oauth2:implicit disabled="true"/>

        默认支持OAuth2提供的5类grant_type, 若不需要任何一类, 将其配置注释掉(或删掉)即可.

        若需要自定义 authorization url, 在 <oauth2:authorization-server > 配置中添加authorization-endpoint-url,如: authorization-endpoint-url="/oauth2/authorization"

        若需要自定义 token url, 在 <oauth2:authorization-server > 配置中添加token-endpoint-url配置, 如:token-endpoint-url="/oauth2/my_token"

    -->
    <oauth2:authorization-server client-details-service-ref="clientDetailsService" token-services-ref="tokenServices"
                                 user-approval-handler-ref="oauthUserApprovalHandler"
                                 user-approval-page="oauth_approval"
                                 error-page="oauth_error">
         <!--后面这几种授权方式应该都会整合进入CompositeTokenGranter的List<TokenGranter>属性当中的-->  
        <oauth2:authorization-code authorization-code-services-ref="jdbcAuthorizationCodeServices"/>
        <oauth2:implicit/>
        <oauth2:refresh-token/>
        <oauth2:client-credentials/>
        <oauth2:password/>
    </oauth2:authorization-server>

该元素里面的每个标签可设置每一种authorized-grant-type的行为. 如disable refresh-token的配置为

<oauth2:refresh-token disabled="true"/>
  • Oauth2 AccessDecisionManager配置, 这儿在默认的Spring Security AccessDecisionManager的基础上添加了ScopeVoter

<!--

        扩展Spring Security 默认的 AccessDecisionManager

        添加对OAuth中 scope 的检查与校验

    -->
    <beans:bean id="oauth2AccessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
        <beans:constructor-arg>
            <beans:list>
                <beans:bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/>
                <beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
                <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
            </beans:list>
        </beans:constructor-arg>
    </beans:bean>
  • resource-server配置, 这儿定义两咱不同的resource

  <!--

        每一个资源(resource)的定义, resource-id必须唯一, OauthClientDetails中的resourceIds属性的值由此来的,

        允许一个Client有多个resource-id, 由逗号(,)分隔

        每一个定义会在Security Flow中添加一个位于 PRE_AUTH_FILTER 之前的Filter

    -->
    <!--unity resource server filter-->
    <oauth2:resource-server id="unityResourceServer" resource-id="unity-resource" token-services-ref="tokenServices"/>

    <!--mobile resource server filter-->
    <oauth2:resource-server id="mobileResourceServer" resource-id="mobile-resource" token-services-ref="tokenServices"/>
注意: 每个resource-id的值必须在对应的ClientDetails中resourceIds值中存在
ClientCredentialsTokenEndpointFilter配置, 该Filter将作用于Spring Security的chain 链条中
  <!--

        处理grant_type=client_credentials 的逻辑

        只从请求中获取client_id与client_secret

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

  • /oauth/token 的http 配置, 用于监听该URL的请求, 核心

 

<!--
     OAuth2 URL: /oauth/token   的处理与配置
     一般使用时这里不需要修改, 直接使用即可
    -->
    <http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="oauth2AuthenticationManager"
          entry-point-ref="oauth2AuthenticationEntryPoint" use-expressions="false">
        <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/>
        <anonymous enabled="false"/>
        <http-basic entry-point-ref="oauth2AuthenticationEntryPoint"/>
        <custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER"/>
        <access-denied-handler ref="oauth2AccessDeniedHandler"/>
        <csrf disabled="true"/>
    </http>

  • 针对不同resource的http配置, 由于上面配置了两个resource, 这儿也配置两个

<!--
        对具体的资源(resource)的安全配置逻辑, 包括ROLE, Scope等
        可根据具体的需求添加, 每一类URL pattern 对应具体的resource
        /unity/**  处理资源 unityResourceServer
        /m/**  处理资源 mobileResourceServer
    -->
    <!--unity http configuration-->
    <http pattern="/unity/**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint"
          access-decision-manager-ref="oauth2AccessDecisionManager" use-expressions="false">
        <anonymous enabled="false"/>
        <intercept-url pattern="/unity/**" access="ROLE_UNITY,SCOPE_READ"/>
        <custom-filter ref="unityResourceServer" before="PRE_AUTH_FILTER"/>
        <access-denied-handler ref="oauth2AccessDeniedHandler"/>
        <csrf disabled="true"/>
    </http>
    <!--mobile http configuration-->
    <http pattern="/m/**" create-session="never" entry-point-ref="oauth2AuthenticationEntryPoint"
          access-decision-manager-ref="oauth2AccessDecisionManager" use-expressions="false">
        <anonymous enabled="false"/>
        <intercept-url pattern="/m/**" access="ROLE_MOBILE,SCOPE_READ"/>
        <custom-filter ref="mobileResourceServer" before="PRE_AUTH_FILTER"/>
        <access-denied-handler ref="oauth2AccessDeniedHandler"/>
        <csrf disabled="true"/>
    </http>

   注意每一个http对应不同的resourceServer. access-decison-manager-ref对应Oauth的AccessDecisionManager

  •    默认的http配置,给/oauth/** 设置权限

   

<http disable-url-rewriting="true" use-expressions="false"
          authentication-manager-ref="authenticationManager">
        <intercept-url pattern="/oauth/**" access="ROLE_USER,ROLE_UNITY,ROLE_MOBILE"/>
        <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <!-- 配置表单验证 -->
        <form-login authentication-failure-url="/login.jsp?authentication_error=1" default-target-url="/index.jsp"
                    login-page="/login.jsp" login-processing-url="/login.do"/>
        <logout logout-success-url="/index.jsp" logout-url="/logout.do"/>
        <access-denied-handler error-page="/login.jsp?authorization_error=2"/>
        <anonymous/>
        <!--CSRF 不启用; 实际 场景 推荐启用 -->
        <csrf disabled="true"/>
    </http>

   到此, securiy.xml 配置完毕.

      当然,还有些额外的工作你需要做, 如配置dataSource, 创建数据库, 添加用户用户信息, 管理ClientDetails等等.

     Oauth相关的数据都是存放在数据库, 我们就可以根据表结果创建domain来实现管理.

具体的认证步骤:

步骤1: 从 spring-oauth-server获取 'code'

  image.png

步骤2:引导到授权页面:

image.png

 

http://localhost:8080/spring-oauth-client/authorization_code_callback?code=urj4G3&state=7956ce11-0dda-435d-bc61-f8139c085920

步骤3:授权回调,携带CODE 给客户端 用 'code' 换取 'access_token'

image.png

    image.png

这时候会返回一个access_token:

{"access_token":"4219a91f-45d5-4a07-9e8e-3acbadd0c23e","token_type":"bearer","refresh_token":"d41df9fd-3d36-4a20-b0b7-1a1883c7439d","expires_in":43199,"scope":"read write trust"}

 

这之后再拿着这个access_token去访问资源:

image.png

http://localhost:8080/spring-oauth-server/ unity/user_info?access_token=4219a91f-45d5-4a07-9e8e-3acbadd0c23e

刷新access_token:

&client_secret=mobile&grant_type=refresh_token&refresh_token=b36f4978-a172-4aa8-af89-60f58abe3ba1

本章的Oauth参考开源项目:    https://gitee.com/shengzhao/spring-oauth-server  配置解析,详细的可以参考这个项目。

https://www.cnblogs.com/xingxueliao/p/5911292.html

OAuth2.0实战之微信授权篇

http://www.imooc.com/article/17696

发表评论