目录授权服务器@EnableAuthorizationServer解析@EnableResourceServer AuthorizationEndpoint生成授权码TokenEndpoint 生成token流程资源服务器@EnableResourceServer ResourceServerConfiguration ResourceServerSecurityConfigurer OAuth2AuthenticationProcessingFilter授权服务器@EnableAuthorizationServer解析我们都知道 一个授权认证服务器最最核心的就是 @EnableAuthorizationServer , 那么 @EnableAuthorizationServer 主要做了什么呢?
我们看下 @EnableAuthorizationServer 源码:@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class}) public @interface EnableAuthorizationServer {}我们可以看到其源码内部导入了AuthorizationServerEndpointsConfiguration和AuthorizationServerSecurityConfiguration这2个配置类。 接下来我们分别看下这2个配置类具体做了什么。
AuthorizationServerEndpointsConfiguration@Configuration@Import(TokenKeyEndpointRegistrar.class) public class AuthorizationServerEndpointsConfiguration {// 省略 其他相关配置代码....
// 1、 AuthorizationEndpoint 创建@Bean public AuthorizationEndpoint authorizationEndpoint() throws Exception {AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));authorizationEndpoint.setTokenGranter(tokenGranter());authorizationEndpoint.setClientDetailsService(clientDetailsService);authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());authorizationEndpoint.setRedirectResolver(redirectResolver());return authorizationEndpoint;}// 2、 TokenEndpoint 创建@Bean public TokenEndpoint tokenEndpoint() throws Exception {TokenEndpoint tokenEndpoint = new TokenEndpoint();tokenEndpoint.setClientDetailsService(clientDetailsService);tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());tokenEndpoint.setTokenGranter(tokenGranter());tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());return tokenEndpoint;}// 省略 其他相关配置代码....通过源码我们可以很明确的知道:AuthorizationEndpoint用于服务授权请求。预设地址:/oauth/authorize。
TokenEndpoint用于服务访问令牌的请求。预设地址:/oauth/token。
AuthorizationServerSecurityConfiguration ClientDetailsService : 内部仅有 loadClientByClientId 方法。从方法名我们就可知其是通过 clientId 来获取 Client 信息, 官方提供 JdbcClientDetailsService、InMemoryClientDetailsService 2个实现类,我们也可以像UserDetailsService 一样编写自己的实现类。
UserDetailsService : 内部仅有 loadUserByUsername 方法。这个类不用我再介绍了吧。不清楚得同学可以看下我之前得文章。
ClientDetailsUserDetailsService : UserDetailsService子类,内部维护了 ClientDetailsService 。其 loadUserByUsername 方法重写后调用ClientDetailsService.loadClientByClientId()。
ClientCredentialsTokenEndpointFilter** 作用与 UserNamePasswordAuthenticationFilter 类似,通过拦截 /oauth/token 地址,获取到 clientId 和 clientSecret 信息并创建 UsernamePasswordAuthenticationToken 作为 AuthenticationManager.authenticate() 参数 调用认证过程。
整个认证过程唯一最大得区别在于 DaoAuthenticationProvider.retrieveUser() 获取认证用户信息时调用的是 ClientDetailsUserDetailsService,根据前面讲述的其内部其实是调用ClientDetailsService 获取到客户端信息。
@EnableResourceServer@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import({ResourceServerConfiguration.class}) public @interface EnableResourceServer {}从源码中我们可以看到其导入了 ResourceServerConfiguration 配置类,这个配置类最核心的配置是 应用了ResourceServerSecurityConfigurer,我这边贴出 ResourceServerSecurityConfigurer 源码 最核心的配置代码如下:public void configure(HttpSecurity http) throws Exception {AuthenticationManager oauthAuthenticationManager = this.oauthAuthenticationManager(http);this.resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();this.resourcesServerFilter.setAuthenticationEntryPoint(this.authenticationEntryPoint);this.resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);if (this.eventPublisher != null) {this.resourcesServerFilter.setAuthenticationEventPublisher(this.eventPublisher);}if (this.tokenExtractor != null) {this.resourcesServerFilter.setTokenExtractor(this.tokenExtractor);}this.resourcesServerFilter = (OAuth2AuthenticationProcessingFilter)this.postProcess(this.resourcesServerFilter);this.resourcesServerFilter.setStateless(this.stateless);((HttpSecurity)http.authorizeRequests().expressionHandler(this.expressionHandler).and()).addFilterBefore(this.resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class).exceptionHandling().accessDeniedHandler(this.accessDeniedHandler).authenticationEntryPoint(this.authenticationEntryPoint);}private AuthenticationManager oauthAuthenticationManager(HttpSecurity http) {OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();if (this.authenticationManager != null) {if (!(this.authenticationManager instanceof OAuth2AuthenticationManager)) {return this.authenticationManager;}oauthAuthenticationManager = (OAuth2AuthenticationManager)this.authenticationManager;}oauthAuthenticationManager.setResourceId(this.resourceId);oauthAuthenticationManager.setTokenServices(this.resourceTokenServices(http));oauthAuthenticationManager.setClientDetailsService(this.clientDetails());return oauthAuthenticationManager;}源码中最核心的 就是 官方文档中介绍的 OAuth2AuthenticationProcessingFilter 过滤器, 其配置分3步:1、 创建 OAuth2AuthenticationProcessingFilter 过滤器 对象2、 创建 OAuth2AuthenticationManager 对象 对将其作为参数设置到 OAuth2AuthenticationProcessingFilter 中3、 将 OAuth2AuthenticationProcessingFilter 过滤器添加到过滤器链上AuthorizationEndpoint生成授权码@RequestMapping(value = "/oauth/authorize") public ModelAndView authorize(Map
authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,(Authentication) principal);boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);authorizationRequest.setApproved(approved);if (authorizationRequest.isApproved()) {if (responseTypes.contains("token")) {return getImplicitGrantResponse(authorizationRequest);}if (responseTypes.contains("code")) {// 7 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,(Authentication) principal));}}model.put(AUTHORIZATION_REQUEST_ATTR_NAME, authorizationRequest);model.put(ORIGINAL_AUTHORIZATION_REQUEST_ATTR_NAME, unmodifiableMap(authorizationRequest));return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);}catch (RuntimeException e) {sessionStatus.setComplete();throw e;}}1、 通过 OAuth2RequestFactory 从 参数中获取信息创建 AuthorizationRequest 授权请求对象2、 判断 principal 是否 已授权 : /oauth/authorize 设置为无权限访问 ,所以要判断,如果 判断失败则抛出 InsufficientAuthenticationException (AuthenticationException 子类),其异常会被 ExceptionTranslationFilter 处理 ,最终跳转到 登录页面,这也是为什么我们第一次去请求获取 授权码时会跳转到登陆界面的原因3、 通过 ClientDetailsService.loadClientByClientId() 获取到 ClientDetails 客户端信息4、 获取参数中的回调地址并且与系统配置的回调地址(步骤3获取到的client信息)对比5、 与步骤4一样 验证 scope 6、 检测该客户端是否设置自动 授权(即 我们配置客户端时配置的 autoApprove(true))
7、 由于我们设置 autoApprove(true) 则 调用 getAuthorizationCodeResponse() 方法生成code码并回调到设置的回调地址8、 真实生成Code 的方法时 generateCode(AuthorizationRequest authorizationRequest, Authentication authentication) 方法: 其内部是authorizationCodeServices.createAuthorizationCode()方法生成code的TokenEndpoint 生成token@RequestMapping(value = "/oauth/token", method=RequestMethod.POST) public ResponseEntity
if (!(principal instanceof Authentication)) {throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");}// 2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置客户端信息String clientId = getClientId(principal);ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);// 3、 通过客户端信息生成 TokenRequest 对象TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);......
// 4、 调用 TokenGranter.grant()方法生成 OAuth2AccessToken 对象(即token)
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);if (token == null) {throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());}// 5、 返回token return getResponse(token);}1、 验证 用户信息 (正常情况下会经过 ClientCredentialsTokenEndpointFilter 过滤器认证后获取到用户信息 )
2、 通过 ClientDetailsService().loadClientByClientId() 获取系统配置的客户端信息
3、 通过客户端信息生成
TokenRequest对象4、 将步骤3获取到的 TokenRequest 作为TokenGranter.grant() 方法参照 生成 OAuth2AccessToken 对象(即token)
5、 返回 token TokenGranter TokenGranter的设计思路是使用CompositeTokenGranter管理一个List列表,每一种grantType对应一个具体的真正授权者,CompositeTokenGranter 内部就是在循环调用五种TokenGranter实现类的 grant方法,而granter内部则是通过grantType来区分是否是各自的授权类型。
五种类型分别是:
ResourceOwnerPasswordTokenGranter ==> password密码模式AuthorizationCodeTokenGranter ==> authorization_code授权码模式ClientCredentialsTokenGranter ==> client_credentials客户端模式ImplicitTokenGranter ==> implicit简化模式RefreshTokenGranter ==>refresh_token 刷新token专用OAuth2AccessToken@JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class)@JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class)@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class)@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) public interface OAuth2AccessToken {String BEARER_TYPE = "Bearer";String OAUTH2_TYPE = "OAuth2";String ACCESS_TOKEN = "access_token";String TOKEN_TYPE = "token_type";String EXPIRES_IN = "expires_in";String REFRESH_TOKEN = "refresh_token";String SCOPE = "scope";}AuthorizationServerTokenServicespublic interface AuthorizationServerTokenServices {//创建OAuth2AccessToken createAccessToken(OAuth2Authentication var1) throws AuthenticationException;//刷新OAuth2AccessToken refreshAccessToken(String var1, TokenRequest var2) throws AuthenticationException;//获取OAuth2AccessToken getAccessToken(OAuth2Authentication var1);}流程
资源服务器@EnableResourceServer@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import({ResourceServerConfiguration.class}) public @interface EnableResourceServer {}ResourceServerConfigurationprotected void configure(HttpSecurity http) throws Exception {ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();ResourceServerTokenServices services = this.resolveTokenServices();if (services != null) {resources.tokenServices(services);} else if (this.tokenStore != null) {resources.tokenStore(this.tokenStore);} else if (this.endpoints != null) {resources.tokenStore(this.endpoints.getEndpointsConfigurer().getTokenStore());}if (this.eventPublisher != null) {resources.eventPublisher(this.eventPublisher);}Iterator var4 = this.configurers.iterator();ResourceServerConfigurer configurer;while(var4.hasNext()) {configurer = (ResourceServerConfigurer)var4.next();configurer.configure(resources);}((HttpSecurity)((HttpSecurity)http.authenticationProvider(new AnonymousAuthenticationProvider("default")).exceptionHandling().accessDeniedHandler(resources.getAccessDeniedHandler()).and()).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()).csrf().disable();http.apply(resources);if (this.endpoints != null) {http.requestMatcher(new ResourceServerConfiguration.NotOAuthRequestMatcher(this.endpoints.oauth2EndpointHandlerMapping()));}var4 = this.configurers.iterator();while(var4.hasNext()) {configurer = (ResourceServerConfigurer)var4.next();configurer.configure(http);}if (this.configurers.isEmpty()) {((AuthorizedUrl)http.authorizeRequests().anyRequest()).authenticated();}}ResourceServerSecurityConfigurerpublic void configure(HttpSecurity http) throws Exception {AuthenticationManager oauthAuthenticationManager = this.oauthAuthenticationManager(http);//创建OAuth2核心过滤器this.resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();this.resourcesServerFilter.setAuthenticationEntryPoint(this.authenticationEntryPoint);//设置OAuth2的身份认证处理器,没有交给spring管理(避免影响非普通的认证流程) this.resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);if (this.eventPublisher != null) {this.resourcesServerFilter.setAuthenticationEventPublisher(this.eventPublisher);}if (this.tokenExtractor != null) {//设置TokenExtractor默认的实现BearerTokenExtractor this.resourcesServerFilter.setTokenExtractor(this.tokenExtractor);}this.resourcesServerFilter = (OAuth2AuthenticationProcessingFilter)this.postProcess(this.resourcesServerFilter);this.resourcesServerFilter.setStateless(this.stateless);// @formatter:off ((HttpSecurity)http.authorizeRequests().expressionHandler(this.expressionHandler).and()).addFilterBefore(this.resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class).exceptionHandling().accessDeniedHandler(this.accessDeniedHandler).authenticationEntryPoint(this.authenticationEntryPoint);}OAuth2AuthenticationProcessingFilterpublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {boolean debug = logger.isDebugEnabled();HttpServletRequest request = (HttpServletRequest)req;HttpServletResponse response = (HttpServletResponse)res;try {//从请求中取出身份信息,即access_token,封装到 PreAuthenticatedAuthenticationToken Authentication authentication = this.tokenExtractor.extract(request);if (authentication == null) {.....
} else {request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());if (authentication instanceof AbstractAuthenticationToken) {AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));}//认证身份Authentication authResult = this.authenticationManager.authenticate(authentication);if (debug) {logger.debug("Authentication success: " + authResult);}this.eventPublisher.publishAuthenticationSuccess(authResult);//将身份信息绑定到SecurityContextHolder中SecurityContextHolder.getContext().setAuthentication(authResult);}} catch (OAuth2Exception var9) {SecurityContextHolder.clearContext();if (debug) {logger.debug("Authentication request failed: " + var9);}this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));return;}chain.doFilter(request, response);}整个filter步骤最核心的是下面2个:1、 调用 tokenExtractor.extract() 方法从请求中解析出token信息并存放到 authentication 的 principal 字段 中2、 调用 authenticationManager.authenticate() 认证过程: 注意此时的 authenticationManager 是 OAuth2AuthenticationManager在解析@EnableResourceServer 时我们讲过 OAuth2AuthenticationManager 与 OAuth2AuthenticationProcessingFilter 的关系,这里不再重述,我们直接看下 OAuth2AuthenticationManager 的 authenticate() 方法实现:public Authentication authenticate(Authentication authentication) throws AuthenticationException {if (authentication == null) {throw new InvalidTokenException("Invalid token (token not found)");}// 1、 从 authentication 中获取 token String token = (String) authentication.getPrincipal();// 2、 调用 tokenServices.loadAuthentication() 方法 通过 token 参数获取到 OAuth2Authentication 对象 ,这里的tokenServices 就是我们资源服务器配置的。
OAuth2Authentication auth = tokenServices.loadAuthentication(token);if (auth == null) {throw new InvalidTokenException("Invalid token: " + token);}Collection
3、 检测客户端信息,由于我们采用授权服务器和资源服务器分离的设计,所以这个检测方法实际没有检测4、 设置认证成功标识并返回 ,注意返回的是 OAuth2Authentication (Authentication 子类)。
到此这篇关于Java中关于OAuth2.0的原理分析的文章就介绍到这了,更多相关OAuth2.0原理 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:Java中OAuth2.0第三方授权原理与实战SpringSecurity oAuth2.0的四种模式(小结)基于OAuth2.0授权系统的验证码功能的实现Spring OAuth2.0 单元测试解决方案详解laravel passport OAuth2.0的4种模式使用Springboot搭建OAuth2.0 Server的方法示例Spring Security Oauth2.0 实现短信验证码登录示例nodejs实现OAuth2.0授权服务认证
