spring security for oauth 2 Developers Guide
这段时间在看spring security for oauth 2资料真少。只好看官方的Developers Guide 和他们提供的demo。
在这里做一下Developers Guide的总结
oauth2 共分三个部分
1.是oauth的授权与认证中心(文档叫提供端)。
2.是oauth的资源中心
3.是oauth的客户端
1.是oauth的授权与认证中心(文档叫提供端)
授权与认证端有两个重要的功能和一些配置功能:
1.1客户端访问被保护的资源是需要得到一个授权,“授权与认证端”提供一个这样的授权功能,也就是第一张通行证授权码。
默认的路径是\oauth\authorize,这个路径对应的代码是AuthorizationEndpoint的authorize(...)方法。
主要作用是,用户通过客户端访问受保护的资源时,客户端会先问问“授权与认证端”当前用户有这个权限访问受保护资源吗,“授权与认证端”就会转到一个输入用户名密码的页面让当前用户输入用户名密码,“授权与认证端”验证用户名和密码,验证无误后就要告诉客户端这个人有权限访问受保护的资源,并给客户端一个第一道门的通行证,客户端可以拿这个第一道门的通行证换第二道们的通行证,(oauth2的授权码模式(authorization code)需要这个流程)
1.2是提供第二道门的通行证 token。
默认路径是\oauth\token,对应的代码是TokenEndpoint的ResponseEntity(...)函数。有了第二道门的通行证后客户端就可以拿着它去访问受保护的资源了。当然资源服务器还得确认你这个通行证真假。
1.3除了以上两个重要的功能外,“授权与认证端”还得做一些配置。
1.3.1 对于客户端的配置ClientDetailsServiceConfigurer
例如配置授权类型是什么样(有四种授权类型授权码模式(authorization code)简化模式(implicit)密码模式(resource owner password credentials)客户端模式(client credentials))。配置好授权类型后,有客户端需要“授权与认证端”授权时就按照配置好的授权流程走。当然了如果某个客户端说我不要你配置的授权类型,我需一个我自己独有的授权类型,这也能配置。
怎么配置呢?实例代码如下:这是官方的demo。
@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserApprovalHandler userApprovalHandler;
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Value("${tonr.redirect:http://localhost:80/tonr2/sparklr/redirect}")
private String tonrRedirectUri;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// @formatter:off
clients.inMemory().withClient("tonr")
.resourceIds(SPARKLR_RESOURCE_ID)
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT")
.scopes("read", "write")
.secret("secret")
.and()
.withClient("tonr-with-redirect")
.resourceIds(SPARKLR_RESOURCE_ID)
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT")
.scopes("read", "write")
.secret("secret")
.redirectUris(tonrRedirectUri)
.and()
.withClient("my-client-with-registered-redirect")
.resourceIds(SPARKLR_RESOURCE_ID)
.authorizedGrantTypes("authorization_code", "client_credentials")
.authorities("ROLE_CLIENT")
.scopes("read", "trust")
.redirectUris("http://anywhere?key=value")
.and()
.withClient("my-trusted-client")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(60)
.and()
.withClient("my-trusted-client-with-secret")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.secret("somesecret")
.and()
.withClient("my-less-trusted-client")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.and()
.withClient("my-less-trusted-autoapprove-client")
.authorizedGrantTypes("implicit")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.autoApprove(true);
// @formatter:on
}
1.3.2 AuthorizationServerSecurityConfigurer 配置
这个干什么的没搞懂,先放着。
1.3.3AuthorizationServerEndpointsConfigurer 配置
1.3.3.1配置第二张通行证token的存储方式,用户填写用户名密码以后的处理,以及用户信息的存储方式authenticationManager。重要的是这还能设置AuthorizationServerTokenServices, 下面的代码里没有设置这个参数,既然没有那就用默认的参数呗,默认的是DefaultTokenServices。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
.authenticationManager(authenticationManager);
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
我们来说说 DefaultTokenService,这里面做了什么事呢,当有一个请求说给我第二张通行证token的时候,这个token就是从createAccessToken(...)方法中产生的。这个方法先查查自己的数据库或内存(默认是使用内存的存储方式,上面代码里也是设置的内存存储return new InMemoryTokenStore();也可以设置JdbcTokenStore存储,也就是数据库存储)中有没有这个请求的token,有没有过期,如果有并且没有过期就给返回给他,如果没有就自己创建一个返回给他。
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
上面这句话是用来创建token的。
private TokenGranter tokenGranter() {
if (tokenGranter == null) {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices,
clientDetails, requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetails, requestFactory));
}
tokenGranter = new CompositeTokenGranter(tokenGranters);
}
return tokenGranter;
}
受保护的资源(或称为远程资源)可以使用OAuth2ProtectedResourceDetails类型的实体bean定义。 一个受保护的资源有以下属性:
-
id:资源id。它仅在客户端搜索资源的时候使用;在OAuth协议中它从未被用到。它也被用作bean的id。
-
clientId:OAuth客户端id。OAuth提供端依赖这个id来识别客户端。
clientId和1.3.1代码中配置的要一致,同时根据clientId也区分不同客户端的授权方式。下面是客户端的配置部分代码。 -
clientSecret:与资源有关的秘密。默认情况下,该值不为空。
-
accessTokenUri:提供访问口令的OAuth提供者终端的统一资源标识符(URI)。
-
scope:以逗号分隔的字符串列表,标识可访问资源的范围。默认情况下,该值为空。
-
clientAuthenticationScheme: 客户端对访问的令牌终端授权时使用的机制。 建议值: "http_basic" 和 "form"。 默认值: "http_basic"。
不同的授权类型有不同的实现OAuth2ProtectedResourceDetails (对于client_credentials授权类型,使用ClientCredentialsResource )的方式。如下面第二段代码。
对于需要进行用户身份验证的授权类型,还有一个属性:
-
userAuthorizationUri: 用户访问资源需要身份验证时跳转页面的URI。 注意这个字段不是必填的,它依赖于被支持的OAuth 2的配置文件类型。
@Bean
public OAuth2ProtectedResourceDetails sparklr() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("sparklr/tonr");
details.setClientId("tonr");
details.setClientSecret("secret");
details.setAccessTokenUri(accessTokenUri);
details.setUserAuthorizationUri(userAuthorizationUri);
details.setScope(Arrays.asList("read", "write"));
return details;
}
@Bean
public OAuth2ProtectedResourceDetails trusted() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setId("sparklr/trusted");
details.setClientId("my-client-with-registered-redirect");
details.setAccessTokenUri(accessTokenUri);
details.setScope(Arrays.asList("trust"));
return details;
}
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
request.setAttribute(CURRENT_URI, calculateCurrentUri(request));
try {
chain.doFilter(servletRequest, servletResponse);
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
.getFirstThrowableOfType(
UserRedirectRequiredException.class, causeChain);
if (redirect != null) {
redirectUser(redirect, request, response);
} else {
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
throw new NestedServletException("Unhandled exception", ex);
}
}
}