集成 Spring Security OAuth2.0

最近想在之前的系统里加入oauth2.0服务,于是找到spring security oauth项目主页,发现只有例子和开发人员指南可以参考,例子用的是java config的方式,与现在的系统用xml的方式有点不一致。便自己研究了下,系统里已经集成了spring security,并且用户的认证授权是由CAS提供的。

1、理解 OAuth2.0

1.1 相关引用

理解OAuth2.0 http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

开发人员指南 https://www.oschina.net/translate/oauth-2-developers-guide

1.2 理解 Spring Security 的几个概念:
  1. 认证与授权    认证是对访问主体进行合法化认证,不管用的是CAS用户还是JDBC查询出来的用户还是接下来要说的client,我们都需要进行认证,只是CAS这种方式,用户的认证由CAS确定,授权同样也是可以由CAS确定的。授权是对已经认证的主体赋予相应的权限,使之可以访问对于的资源。
  2. 权限校验    主体请求访问被保护资源时,将被保护资源所需的权限和主体所拥护的权限二者进行比对,如果校验通过则用户可以访问被保护资源,否则拒绝访问。
1.3 for example 

用户 admin 需要访问 /admin/user ,这个资源我们需要用户具备 ROLE_ADMIN 这样的角色才能访问,那么 admin 就要具备这样的角色,如果用户 admin 只是具有 ROLE_USER 角色,他密码用户名正确,我们称他是合法用户,也就是 检查 他的票据(用户名密码或者是CAS的票据)是可以认同的,但你不具备访问/admin/user的权限。这个时候返回的应该是403,而不是401。

2、准备工作

2.1 添加依赖

这里加入的是2.0.12.RELEASE,因为parent里有platform-bom的Athens-SR1版本。

<dependency>
	<groupId>org.springframework.security.oauth</groupId>
	<artifactId>spring-security-oauth2</artifactId>
</dependency>
2.2 引入命名空间
<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:oauth2="http://www.springframework.org/schema/security/oauth2"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/util
		http://www.springframework.org/schema/util/spring-util.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">

 

3、开始配置

3.1 配置client的认证服务器

这里由于之前集成了CAS,增加 provider 即可。

<authentication-manager alias="authenticationManager">
	<authentication-provider ref="casAuthenticationProvider" />
	<authentication-provider user-service-ref="clientDetailsUserDetailsService" />
</authentication-manager>

这样的方式会生成一个ProviderManager 的 authenticationManager ,ProviderManager 会得到所有的 provider 进行遍历,直到最后没有异常。这里增加的 provider 会对 client 的 id 和 scret进行校验,之前的 provider  处理CAS返回来的信息,一个是对普通用户的认证,一个是对客户端的认证,这两种对于 spring security 来说 都是需要认证的用户。

3.2 client 信息

当然需要知道有什么 有 client 吧。所以 下一步  配置 ClientDetailsUserDetailsService,这里把client当作系统的用户来处理。

<beans:bean id="clientDetailsUserDetailsService"
	class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
	<beans:constructor-arg ref="clientDetailsService" />
</beans:bean>

<oauth2:client-details-service id="clientDetailsService">
	<oauth2:client client-id="123" secret="456" scope="read,write,trust"
		redirect-uri="https://www.baidu.com" authorities="ROLE_CLIENT"
		authorized-grant-types="authorization_code,refresh_token,implicit,client_credentials" />
</oauth2:client-details-service>

这里使用的内存的配置,后期可以修改成 JDBC。建表文件在 github 上  oauth 项目里有。

3.3 配置 TokenServices

既然是rest ,那么就应该是一个无状态的连接。如何确定访问的权限呢,这就通过 token 来识别。配置 TokenServices 、token 的 保存、过期、删除 就由这管理。


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

<beans:bean id="tokenStore"
	class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" />

这里使用的也是内存保存,后期同样需要 修改成 jdbc 的方式。不然重启之后client 又需要用户授权了。

3.4 配置一些默认的处理
<beans:bean id="defaultUserApprovalHandler"
	class="org.springframework.security.oauth2.provider.approval.DefaultUserApprovalHandler" />

<beans:bean id="oAuth2AuthenticationEntryPoint"
	class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint" />

<beans:bean id="oAuth2AccessDeniedHandler"
	class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler" />
3.5 配置 client 的授权服务器
<oauth2:authorization-server
	token-services-ref="defaultTokenServices" client-details-service-ref="clientDetailsService"
	user-approval-handler-ref="defaultUserApprovalHandler">
	<oauth2:authorization-code />
	<oauth2:implicit />
	<oauth2:refresh-token />
	<oauth2:client-credentials />
	<oauth2:password disabled="true" />
</oauth2:authorization-server>

这里禁用了 password 的方式,因为 用户信息是从CAS那里获取的,使用这种方式这样的没有办法认证的,如果一定需要,还要开发一个与CAS服务交互,或者远程调用,或者直接连接数据库,我的另一篇博客里有替代的方式 https://my.oschina.net/taoyuanping/blog/809157

3.6 配置 资源服务器
<oauth2:resource-server id="apiResourceServer"
	resource-id="api-Resource" token-services-ref="defaultTokenServices" />
3.7 配置 http 标签

<http pattern="/oauth/token" create-session="stateless"
	entry-point-ref="oAuth2AuthenticationEntryPoint">
	<csrf disabled="true" />
	<anonymous enabled="false" />
	<intercept-url pattern="/oauth/token" access="hasRole('CLIENT')" />
	<custom-filter ref="clientCredentialsTokenEndpointFilter"
		before="BASIC_AUTH_FILTER" />
	<access-denied-handler ref="oAuth2AccessDeniedHandler" />
</http>

开启client 授权服务器后,框架会自动配置 几个 url ,包括 授权、获取令牌。这里需要对 获取令牌的 请求做认证

加入过滤器 clientCredentialsTokenEndpointFilter

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

这个过滤器可以对client 进行合法认证,总不能随随便便发个client_id我就发个令牌给你吧。需要关闭 csrf ,用不着的。只有具备 ROLE_CLIENT 的角色才能使用。

3.8 配置资源访问权限

client 的认证服务  授权服务 资源服务 都有了,还需要什么 ,还需要一个 可以对访问资源服务器的入口,或者说对  token 的校验。

<http pattern="/api/**" create-session="stateless"
	entry-point-ref="oAuth2AuthenticationEntryPoint">
	<csrf disabled="true" />
	<anonymous enabled="false" />
	<intercept-url pattern="/api/**" access="hasRole('USER')" />
	<custom-filter ref="apiResourceServer" before="PRE_AUTH_FILTER" />
	<access-denied-handler ref="oAuth2AccessDeniedHandler" />
</http>
  1. 这里的 hasRole('USER') 其实角色 资源的所有着具备的角色,资源的所有者授权给client,client 是不是就具备了这样的角色。还有一种oauth的scope的认证。需要 配置 expression-handler。
  2. <custom-filter ref="apiResourceServer" before="PRE_AUTH_FILTER" /> 会生成一个 OAuth2AuthenticationProcessingFilter 的过滤器,这个过滤器负责认证accass_token的有效性。

4、配置MVC


@Controller
@RequestMapping("/api")
public class UserApi {

	@Autowired
	private UserService userService;

	@ResponseBody
	@RequestMapping(value = "/user", method = RequestMethod.POST)
	public User getUser(Principal principal) {
		User user = userService.findByUsername(principal.getName());
		return user;
	}

}

5、 测试

5.1 获取 code

http://www.yakee.net:8080/framework/oauth/authorize?client_id=123&response_type=code

没有登陆,需要用户登陆

 

登陆用户  admin /123456   cas 返回信息 用户具备  user 角色。

<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
    <cas:authenticationSuccess>
        <cas:user>admin</cas:user>
        <cas:attributes> 
            <cas:isFromNewLogin>true</cas:isFromNewLogin>
            <cas:authenticationDate>2016-12-15T19:10:18.258+08:00</cas:authenticationDate>
            <cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>
            <cas:authorities>ROLE_REST_SERVICE</cas:authorities>
            <cas:authorities>ROLE_ADMIN</cas:authorities>
            <cas:authorities>ROLE_USER</cas:authorities>
        </cas:attributes>
    </cas:authenticationSuccess>
</cas:serviceResponse>

 

5.2 admin 用户授权客户端 123

页面难看是难看了,但是功能一个不落。

5.3 返回 授权码 code

返回code 参数  NhSB8O

5.4 使用授权码 获取 token

@Test
public void getToken() {
	Map<String, String> params = new HashMap<String, String>();
	params.put("redirect_uri", "https://www.baidu.com");
	params.put("grant_type", "authorization_code");
	params.put("client_secret", "456");
	params.put("client_id", "123");
	params.put("code", "NhSB8O");

	String rsp = null;

	try {
		rsp = WebUtils.doPost("http://www.yakee.net:8080/framework/oauth/token", params, 3000, 15000);

		System.out.println(rsp);

	} catch (Exception e) {
		e.printStackTrace();
	}

}

返回

{
    "additionalInformation":{},
    "expiration":"2016-12-16 07:17:39",
    "expired":false,
    "expiresIn":43199,
    "refreshToken":{
        "expiration":"2017-01-14 19:17:39",
        "value":"58c44eee-67bb-46b0-b98f-5226d2c81214"
    },
    "scope":["read","write","trust"],
    "tokenType":"bearer",
    "value":"eb7cb64d-02b8-4208-9d01-6ec77265f8a2"
}

过期时间真短。

5.5 使用 access_token  获取用户

@Test
public void getUser() {
	Map<String, String> params = new HashMap<String, String>();
	params.put("access_token", "eb7cb64d-02b8-4208-9d01-6ec77265f8a2");

	String rsp = null;

	try {
		rsp = WebUtils.doPost("http://www.yakee.net:8080/framework/api/user", params, 3000, 15000);

		System.out.println(rsp);

	} catch (Exception e) {
		e.printStackTrace();
	}

}

 

发起请求后 过滤链情况

Request received for POST '/api/user':

org.apache.catalina.connector.RequestFacade@1a793b92

servletPath:/api/user
pathInfo:null
headers: 
accept: text/xml,text/javascript,text/html
user-agent: aop-sdk-java
content-type: application/x-www-form-urlencoded;charset=UTF-8
host: www.yakee.net:8080
connection: keep-alive
content-length: 49


Security filter chain: [
  SecurityContextPersistenceFilter
  WebAsyncManagerIntegrationFilter
  HeaderWriterFilter
  OAuth2AuthenticationProcessingFilter
  SecurityContextHolderAwareRequestFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************

返回

{
    "createDate":"2016-12-12 01:42:38",
    "department":"软件研发部",
    "email":"taoyuanping@yakee.net",
    "enabled":true,
    "id":2,
    "modifyDate":"2016-12-12 01:42:38",
    "name":"陶远平",
    "password":"$2a$10$lH8n4sStC44KjG7GPsrgneBcZp.trl9U8WCH06BERpN6DAx4U30HK",
    "phone":"15220176365",
    "username":"admin"
}

6、后续配置

开发 client 的管理,将信息保存到数据库。将token保存到数据库并且增加过期时间,优化用户授权页面,错误信息等,配置expression-handler,支持oauth的scope权限认证。最后配置https,使用OpenSSL生成自签证书即可,使用http调用rest服务可不是明智之举。

 

转载于:https://my.oschina.net/taoyuanping/blog/806601

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值