关于SpringOauth2理解

7 篇文章 0 订阅
4 篇文章 0 订阅

关于SpringOauth2理解

OAuth2.0说明

官网学习地址:

https://docs.spring.io/spring-security-oauth2-boot/docs/2.2.0.RELEASE/reference/html5/#boot-features-security-oauth2-single-sign-on

角色

在 OAuth2.0 中,有如下角色:

Authorization Server:认证服务器,用于认证用户。如果客户端认证通过,则发放访问资源服务器的令牌

Resource Server:资源服务器,拥有受保护资源。如果请求包含正确的访问令牌,则可以访问资源。

友情提示:提供管理后台、客户端 API 的服务,都可以认为是 Resource Server。

Client:客户端。它请求资源服务器时,会带上访问令牌,从而成功访问资源。

友情提示:Client 可以是浏览器、客户端,也可以是内部服务。

④ Resource Owner:资源拥有者。最终用户,他有访问资源的账号密码

友情提示:可以简单把 Resource Owner 理解成人,她在使用 Client 访问资源。

认证流程

OAuth 2.0 运行流程

  • (A)用户打开客户端以后,客户端要求用户给予授权。

  • (B)用户同意给予客户端授权。

  • (C)客户端使用上一步获得的授权,向认证服务器申请令牌。

  • (D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

  • (E)客户端使用令牌,向资源服务器申请获取资源。

  • (F)资源服务器确认令牌无误,同意向客户端开放资源。

     

源码分析

启动流程

@EnableAuthorizationServer` 此注解可以了解到Springboot enable模式,关于bean注册的SPI原理可以有空看下(个人认为在springboot原理中还是比较重要的,包括自动注入,和enable)

·用于启用授权服务器,在当前应用程序上下文中,它必须是 DispatcherServlet上下文。很多可以使用{authorizationserverconfigurer}类型的@Beans来定制服务器的特性(例如,通过扩展{@link AuthorizationServerConfigurerAdapter}。用户负责保护使用普通Spring安全特性( EnableWebSecurity)的授权端点(/oauth/authorizeEnableWebSecurity}等,但是令牌端点(/oauth/Token)将使用httpbasic自动保护对客户端凭据进行身份验证。必须通过提供{ ClientDetailsService}通过一个或多个授权服务器配置程序。

@Import()注解在Spring Boot使用较多,相对比的xml引入另一个xml(可以学习下)

我们可以看到其源码内部导入了 AuthorizationServerEndpointsConfigurationAuthorizationServerSecurityConfiguration 这2个配置类。 接下来我们分别看下这2个配置类具体做了什么。

@EnableAuthorizationServer
└── AuthorizationServerEndpointsConfiguration
     └── TokenKeyEndpointRegistrar
├── AuthorizationServerSecurityConfiguration
     └── ClientDetailsServiceConfiguration 
     └── AuthorizationServerEndpointsConfiguration      

一. AuthorizationServerEndpointsConfiguration

提供端点的访问配置对象

1.authorizationEndpoint()用于服务于授权请求。预设地址:/oauth/authorize

2.TokenEndpoint()用于服务访问令牌的请求。预设地址:/oauth/token

3.checkTokenEndpoint()用于服务访问令牌的校验。预设地址:/oauth/check_token

TokenKeyEndpoint.registry.registerBeanDefinition();注册bean


二.AuthorizationServerSecurityConfiguration

(实际这个加载bean的时候要优先AuthorizationServerEndpointsConfiguration)可以看order排序注解

1.@import ClientDetailsServiceConfiguration 构建configurers对象

image-20200912103836842

@Configuration
public class ClientDetailsServiceConfiguration {
​
   @SuppressWarnings("rawtypes")
   private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
    
   @Bean
   public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {
      return configurer;
   }
}

设置clientDetails属性

@Autowired
public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
   for (AuthorizationServerConfigurer configurer : configurers) {
     //继承父类AuthorizationServerConfigurer,但是它下面有空实现,我们来继承子类覆盖就可以
      //AuthorizationServerConfigurerAdapter
      configurer.configure(clientDetails);
   }
}

2.AuthorizationServerEndpointsConfiguration

@PostConstruct
public void init() {
   for (AuthorizationServerConfigurer configurer : configurers) {
      try {
         //设置端点权限配置,我们可以集成重写
         configurer.configure(endpoints);
         //继承父类AuthorizationServerConfigurer,但是它下面有空实现,我们来继承子类覆盖就可以
         //AuthorizationServerConfigurerAdapter
      } catch (Exception e) {
         throw new IllegalStateException("Cannot configure enpdoints", e);
      }
   }
   endpoints.setClientDetailsService(clientDetailsService);
}

AuthorizationServerConfigurer的子类

image-20200912104434489

 

最主要的配置 ClientDetailsService()ClientDetailsService() 以及 ClientCredentialsTokenEndpointFilter()

  1. ClientDetailsService : 内部仅有 loadClientByClientId 方法。从方法名我们就可知其是通过 clientId 来获取 Client 信息, 官方提供 JdbcClientDetailsService、InMemoryClientDetailsService 2个实现类,我们也可以像UserDetailsService 一样编写自己的实现类。

  2. UserDetailsService : 内部仅有 loadUserByUsername 方法。这个类不用我再介绍了吧。不清楚得同学可以看下我之前得文章。

  3. ClientDetailsUserDetailsService : UserDetailsService子类,内部维护了 ClientDetailsService 。其 loadUserByUsername 方法重写后调用ClientDetailsService.loadClientByClientId()。

访问流程

img

 

  1. 用户名和密码模式

    请求curl

    curl 'http://127.0.0.1:8080/auth/oauth/token?grant_type=password&username=admin&password=123456&scope=server&client_id=xxx&client_secret=xxxx' 

    首先debug到org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken()这个类的方法上,删除验证的业务逻辑

        //获取参数中的clientId
        String clientId = getClientId(principal);
        // 根据clientId拿去客户端详细信息(可以实现集成ClientDetailsService下面的子类)
        ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
        //可以看下面代码。实际上是把请求参数和authenticatedClient信息做了个融合
       //TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
        //跳过一些验证可以看到最终构建的是一个tokenRequest对象也就是流程图的地4步
        TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
        //生产token的地方
        //getTokenGranter获取grant_type=password对应的接口,通过CompositeTokenGranter获取合适的
        //TokenGranter,这里调用的抽象类里面的父类 
        // OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
        OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
        return getResponse(token);

     

    //可以看下面代码。实际上是把请求参数和authenticatedClient信息做了个融合
       //TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
    public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
    ​
       String clientId = requestParameters.get(OAuth2Utils.CLIENT_ID);
    ​
       String grantType = requestParameters.get(OAuth2Utils.GRANT_TYPE);
    ​
       Set<String> scopes = extractScopes(requestParameters, clientId);
       TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, scopes, grantType);
       return tokenRequest;
    }
    getTokenGranter()获取对应的grant_type=password找到对应的处理密码模式使用的
    ResourceOwnerPasswordTokenGranter.class
    //包含校验类型下面的4中类型的验证,以及刷新令牌的 TokenGranter
    private final List<TokenGranter> tokenGranters;
    ​
    //在上面tokenGranters选中一个生成token
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
       for (TokenGranter granter : tokenGranters) {
          OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
          if (grant!=null) {
             return grant;
          }
       }
       return null;
    }
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
    ​
       if (!this.grantType.equals(grantType)) {
          return null;
       }
       
       String clientId = tokenRequest.getClientId();
       ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
       validateGrantType(grantType, client);
    ​
       if (logger.isDebugEnabled()) {
          logger.debug("Getting access token for: " + clientId);
       }
       //核心方法,获取accesstoken
       return getAccessToken(client, tokenRequest);
    }
    ​
    //核心方法,获取accesstoken
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
        //getOAuth2Authentication要获取token需要一个OAuth2Authentication对象
        //最后看createAccessToken方法
        //getOAuth2Authentication这个方法有不同的实现,每个登陆方式不同,处理认证的逻辑不一样
        // 创建accesstoken需要2个对象,一个是tokenreQuest,另一个是我服务器认证的信息
        //本类是个抽象类,可以看子类方法实现  密码登录使用的是
        //ResourceOwnerPasswordTokenGranter.getOAuth2Authentication(); 
        //最后创建token
        return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
      }
      
      

     

    @Override
      protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
    ​
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String username = parameters.get("username");
        String password = parameters.get("password");
        // Protect from downstream leaks of password
        parameters.remove("password");
        
        //生成Authentication这个对象 交给authenticationManager去验证。
        Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
          //authenticationManager会去调用实现UserDetailsService
          userAuth = authenticationManager.authenticate(userAuth);
          //这里没有什么东西,就new OAuth2Request
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);    
        //返回OAuth2Authentication认证信息
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
      }
    @Transactional
      public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
        //首先gettoken
        OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
        OAuth2RefreshToken refreshToken = null;
        if (existingAccessToken != null) {
          //失效的token
          if (existingAccessToken.isExpired()) {
            //已经存在的token
            if (existingAccessToken.getRefreshToken() != null) {
              refreshToken = existingAccessToken.getRefreshToken();
              tokenStore.removeRefreshToken(refreshToken);
            }
            tokenStore.removeAccessToken(existingAccessToken);
          }
          else {
            tokenStore.storeAccessToken(existingAccessToken, authentication);
            return existingAccessToken;
          }
        }
          //创建token
        tokenStore.storeAccessToken(accessToken, authentication);
        // In case it was modified
        refreshToken = accessToken.getRefreshToken();
        if (refreshToken != null) {
          tokenStore.storeRefreshToken(refreshToken, authentication);
        }
        return accessToken;
      }
    private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
       DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
       int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
       if (validitySeconds > 0) {
          token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
       }
       token.setRefreshToken(refreshToken);
       token.setScope(authentication.getOAuth2Request().getScope());
        //设置token以及使用token增强器
       return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
    }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值