明确Oauth2的几个概念
这里要先明确几个OAuth2中的几个重要概念:
resource owner: 拥有被访问资源的用户
user-agent: 一般来说就是浏览器
client: 第三方应用
Authorization server: 认证服务器,用来进行用户认证并颁发token
Resource server:资源服务器,拥有被访问资源的服务器,需要通过token来确定是否有权限访问
针对授权类型,主要有以下几种:
authorization_code:授权码类型。
implicit:隐式授权类型。
password:资源所有者(即用户)密码类型。
client_credentials:客户端凭据(客户端ID以及Key)类型。
refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。
授权服务配置
可以用 @EnableAuthorizationServer 注解来配置OAuth2.0 授权服务机制,通过使用@Bean注解的几个方法一起来配置这个授权服务。
下面几个配置是由Spring创建的独立的配置对象,它们会被Spring传入AuthorizationServerConfigurer中:
ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
(译者注:以上的配置可以选择继承AuthorizationServerConfigurerAdapter并且覆写其中的三个configure方法来进行配置。)
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { final JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(this.dataSource); clientDetailsService.setSelectClientDetailsSql("select client_id, client_secret, resources_ids, scope, authorized_grant_types,web_server_redirect_uri, authorities, access_token_validity,refresh_token_validity, addition_information, autoapprove from magic_oauth_client_details where client_id = ?"); clientDetailsService.setFindClientDetailsSql("select client_id, client_secret, resources_ids, scope, authorized_grant_types,web_server_redirect_uri, authorities, access_token_validity,refresh_token_validity, addition_information, autoapprove from magic_oauth_client_details order by client_id"); clients.withClientDetails(clientDetailsService); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(this.tokenEnhancer(), this.jwtAccessTokenConverter())); endpoints.authenticationManager(this.authenticationManager).tokenStore(this.redisTokenStore()).tokenEnhancer(tokenEnhancerChain).reuseRefreshTokens(false); endpoints.exceptionTranslator(this.customWebResponseExceptionTranslator); } @Override public void configure(AuthorizationServerSecurityConfigurer security) { security.allowFormAuthenticationForClients().tokenKeyAccess("isAuthenticated()").checkTokenAccess("isAuthenticated()"); }
ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),有几个重要的属性如下列表:
clientId:(必须的)用来标识客户的Id。
secret:(需要值得信任的客户端)客户端安全码,如果有的话。
scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。
authorities:此客户端可以使用的权限(基于Spring Security authorities)。
客户端详情(Client Details)能够在应用程序运行的时候进行更新,可以通过访问底层的存储服务(例如将客户端详情存储在一个关系数据库的表中,就可以使用 JdbcClientDetailsService)或者通过 ClientDetailsManager 接口(同时你也可以实现 ClientDetailsService 接口)来进行管理。
@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { final JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(this.dataSource); clientDetailsService.setSelectClientDetailsSql("select client_id, client_secret, resources_ids, scope, authorized_grant_types,web_server_redirect_uri, authorities, access_token_validity,refresh_token_validity, addition_information, autoapprove from magic_oauth_client_details where client_id = ?"); clientDetailsService.setFindClientDetailsSql("select client_id, client_secret, resources_ids, scope, authorized_grant_types,web_server_redirect_uri, authorities, access_token_validity,refresh_token_validity, addition_information, autoapprove from magic_oauth_client_details order by client_id"); clients.withClientDetails(clientDetailsService); }
配置授权端点的URL(Endpoint URLs):
AuthorizationServerEndpointsConfigurer 这个配置对象(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 有一个叫做 pathMapping() 的方法用来配置端点URL链接,它有两个参数:
第一个参数:String 类型的,这个端点URL的默认链接。
第二个参数:String 类型的,你要进行替代的URL链接。
以上的参数都将以 “/” 字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping() 方法的第一个参数:
/oauth/authorize:授权端点。
/oauth/token:令牌端点。
/oauth/confirm_access:用户确认授权提交端点。
/oauth/error:授权服务错误信息端点。
/oauth/check_token:用于资源服务访问的令牌解析端点。
/oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(Arrays.asList(this.tokenEnhancer(), this.jwtAccessTokenConverter())); endpoints.authenticationManager(this.authenticationManager).tokenStore(this.redisTokenStore()) .tokenEnhancer(tokenEnhancerChain).reuseRefreshTokens(false); endpoints.exceptionTranslator(this.customWebResponseExceptionTranslator); }
资源服务配置
Spring OAuth提供者是通过Spring Security authentication filter 即验证过滤器来实现的保护,你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上,
并且必须使用 ResourceServerConfigurer 这个配置对象来进行配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性:
tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。
其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。
请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径。
受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。
其他的自定义权限保护规则通过 HttpSecurity 来进行配置。
@Override public void configure(HttpSecurity http) throws Exception { ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry config = http.authorizeRequests(); this.ignoreUrlPropertiesConfig.getUrls().forEach(e -> ((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)config.antMatchers(new String[] { e })).permitAll()); ((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)config .antMatchers(HttpMethod.OPTIONS, new String[] { "/**", "/auth/**", "/admin/**", "/file/**" })) .permitAll() .anyRequest()) .access("@permissionService.hasPermission(request,authentication)"); }
请求的过程
当我们的oauth配置好之后,百分百会调用这个/oath/token url去拿到我们的token,不论你是使用的get,还是post方式,都可以。他的入口在这个类:TokenEndpoint
下面是它的实现:
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity postAccessToken(Principal principal, @RequestParam Map parameters) throws HttpRequestMethodNotSupportedException { if (!(principal instanceof Authentication)) { throw new InsufficientAuthenticationException( "There is no client authentication. Try adding an appropriate authentication filter."); } String clientId = getClientId(principal); ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId); TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient); if (clientId != null && !clientId.equals("")) { // Only validate the client details if a client authenticated during this // request. if (!clientId.equals(tokenRequest.getClientId())) { // double check to make sure that the client ID in the token request is the same as that in the // authenticated client throw new InvalidClientException("Given client ID does not match authenticated client"); } } if (authenticatedClient != null) { oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient); } if (!StringUtils.hasText(tokenRequest.getGrantType())) { throw new InvalidRequestException("Missing grant type"); } if (tokenRequest.getGrantType().equals("implicit")) { throw new InvalidGrantException("Implicit grant type not supported from token endpoint"); } if (isAuthCodeRequest(parameters)) { // The scope was requested or determined during the authorization step if (!tokenRequest.getScope().isEmpty()) { logger.debug("Clearing scope of incoming token request"); tokenRequest.setScope(Collections. emptySet()); } } if (isRefreshTokenRequest(parameters)) { // A refresh token has its own default scopes, so we should ignore any added by the factory here. tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE))); } OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest); if (token == null) { throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType()); } return getResponse(token); }
其调用了DefaultTokenServices的createAccessToken方法,里面则使用了tokenStore.getAccessToken(authentication)来获取的token,这个tokenStore具体是哪个实现类的对象,还要看我们在认证服务器(即继承了AuthorizationServerConfigurerAdapter类),里面的bean:tokenStore。
tokenStore.getAccessToken(authentication)后,发现里面这句话,生成了token,我的token是存在redis的,他会先在reids里面找如果有token就拿出来,没有或者失效了,就重新生成一个。