大话微服务:Spring Cloud gateway+OAuth2 实现单点登录和权限控制(五)OAuth2 认证服务中心的开发

1、前言(单点登录的原理)

     我们在做微服务架构中,一般会研发一个认证中心的应用(即一个微服务),其实这就是单点登录(Single Sign on,即SSO)。这个认证中心实现在多系统应用构成的集群中,登录其中任意一个系统,其它系统自动得到授权,从而无需再次登录,从而实现了单点的登录与单点注销两部分。

     认证中心是一个独立的应用,即微服务,只有它能接受用户名和密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,so认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理。

2、OAuth2的实现,即做一个认证中心,或叫授权服务器的微服务

     oauth2通常分为二块:认证服务和资源服务,这二个可以放在同一个应用中,也可以分为作为应用。即可以把认证中心做成一个微服务。

2.1 授权服务的实现

  2.1.1.主要复写三个方法:

ClientDetailsServiceConfigurer:这个configurer定义了客户端细节服务。客户详细信息可以被初始化,为了灵活通用客户端的配置信息放在数据库表oauth_client_details(OAuth2自带的),通过jdbc引入,主要字段有:resource_ids(资源id标识),client_id,(相当于AppId)client_secret(即密钥,相当于AppSecret),scope(read/write/trust,多个权限逗号分开),authorized_grant_types(四种认证方式用哪一些),authorities(访问资源所需要的权限)等。
AuthorizationServerSecurityConfigurer:在令牌端点上定义了安全约束,这个一般配合WebSecurityConfig一起使用。
AuthorizationServerEndpointsConfigurer:定义了授权和令牌端点和令牌服务。

/**
 * 声明 ClientDetails实现
 */
@Bean
public RedisClientDetailsService redisClientDetailsService(DataSource dataSource , RedisTemplate<String, Object> redisTemplate ) {
    RedisClientDetailsService clientDetailsService = new RedisClientDetailsService(dataSource);
    clientDetailsService.setRedisTemplate(redisTemplate);
    return clientDetailsService;
}


@Bean
public RandomValueAuthorizationCodeServices authorizationCodeServices(RedisTemplate<String, Object> redisTemplate) {
    RedisAuthorizationCodeServices redisAuthorizationCodeServices = new RedisAuthorizationCodeServices();
    redisAuthorizationCodeServices.setRedisTemplate(redisTemplate);
    return redisAuthorizationCodeServices;
}

 

  /**
       * 配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory
       */
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
   
   
   //通用处理
   endpoints.tokenStore(tokenStore).authenticationManager(authenticationManager)
          // 支持
          .userDetailsService(userDetailsService);
   
   if(tokenStore instanceof JwtTokenStore){
      endpoints.accessTokenConverter(jwtAccessTokenConverter);
   }
           
          //处理授权码
          endpoints.authorizationCodeServices(authorizationCodeServices);
          // 处理 ExceptionTranslationFilter 抛出的异常
          endpoints.exceptionTranslator(webResponseExceptionTranslator);

      }

      /**
       * 配置应用名称 应用id
       * 配置OAuth2的客户端相关信息
       */
      @Override
      public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

     
          clients.withClientDetails(redisClientDetailsService);
          redisClientDetailsService.loadAllClientToCache();
      }

      /**
       * 对应于配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器
       */
      @Override
      public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
       
          // url:/oauth/token_key,exposes
          security.tokenKeyAccess("permitAll()")
                  /// public key for token
                  /// verification if using
                  /// JWT tokens
                  // url:/oauth/check_token
                  .checkTokenAccess("isAuthenticated()")
                  // allow check token
                  .allowFormAuthenticationForClients();

      }

2.1.2.开启spring security,即自定义WebSecurityConfigurerAdapter。

2.1.3. 自定义用户认证的实现:

     认证是由AuthenticationManager 来管理的,即自定义AuthenticationProvider。注意AuthenticationManager 中可以定义有多个 AuthenticationProvider。

2.1.4 自定义UserDetailsService

自定义需要实现UserDetailsService接口,并且重写loadUserByUsername方法。返回的用户信息需要实现UserDatails接口。

2.2资源服务的实现

ResourceServerSecurityConfigurer主要配置内容:

  • tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌业务逻辑服务
  • resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证
  • tokenExtractor 令牌提取器用来提取请求中的令牌
  • 请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径
  • 受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)
  • 其他的自定义权限保护规则通过 HttpSecurity 来进行配置

例如代码:

@Configuration
@EnableResourceServer
@EnableConfigurationProperties(PermitUrlProperties.class)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private PermitUrlProperties permitUrlProperties;
    @Autowired(required = false)
    private TokenStore tokenStore;
    @Autowired 
   private ObjectMapper objectMapper ; //springmvc启动时自动装配json处理类
    
    @Autowired
   private OAuth2WebSecurityExpressionHandler expressionHandler;

    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/health");
        web.ignoring().antMatchers("/oauth/user/token");
        web.ignoring().antMatchers("/oauth/client/token");
    }
    
    @Override
   public void configure(ResourceServerSecurityConfigurer resources) throws Exception {

      if (tokenStore != null) {
         resources.tokenStore(tokenStore);
      }  
      resources.stateless(true);
      resources.expressionHandler(expressionHandler);
      // 自定义异常处理端口 
      resources.authenticationEntryPoint(new AuthenticationEntryPoint() {

         @Override
         public void commence(HttpServletRequest request, HttpServletResponse response,
               AuthenticationException authException) throws IOException, ServletException {
            
            Map<String ,String > rsp =new HashMap<>();  
            
            response.setStatus(HttpStatus.UNAUTHORIZED.value() );
            
            rsp.put("resp_code", HttpStatus.UNAUTHORIZED.value() + "") ;
                rsp.put("resp_msg", authException.getMessage()) ;
                
                response.setContentType("application/json;charset=UTF-8");
             response.getWriter().write(objectMapper.writeValueAsString(rsp));
             response.getWriter().flush();
             response.getWriter().close();

         }
      });
      resources.accessDeniedHandler(new OAuth2AccessDeniedHandler(){
           
           @Override
           public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException) throws IOException, ServletException {

              Map<String ,String > rsp =new HashMap<>();  
              response.setContentType("application/json;charset=UTF-8");

               response.setStatus(HttpStatus.UNAUTHORIZED.value() );
            
            rsp.put("resp_code", HttpStatus.UNAUTHORIZED.value() + "") ;
                rsp.put("resp_msg", authException.getMessage()) ;
                
                response.setContentType("application/json;charset=UTF-8");
             response.getWriter().write(objectMapper.writeValueAsString(rsp));
             response.getWriter().flush();
             response.getWriter().close();
               
           }
       });
      
   }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatcher(
                /**
                 * 判断来源请求是否包含oauth2授权信息
                 */
                new RequestMatcher() {
                    private AntPathMatcher antPathMatcher = new AntPathMatcher();

                    @Override
                    public boolean matches(HttpServletRequest request) {
                        // 请求参数中包含access_token参数
                        if (request.getParameter(OAuth2AccessToken.ACCESS_TOKEN) != null) {
                            return true;
                        }

                        // 头部的Authorization值以Bearer开头
                        String auth = request.getHeader(UaaConstant.AUTHORIZTION);
                        if (auth != null) {
                            if (auth.startsWith(OAuth2AccessToken.BEARER_TYPE)) {
                                return true;
                            }
                        }
                        
                        // 认证中心url特殊处理,返回true的,不会跳转login.html页面
                        if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/userinfo")) {
                            return true;
                        }
                        if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/remove/token")) {
                            return true;
                        }
                        if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/get/token")) {
                            return true;
                        }
                        if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/refresh/token")) {
                            return true;
                        }

                        if (antPathMatcher.match(request.getRequestURI(), "/api-auth/oauth/token/list")) {
                            return true;
                        }

                        if (antPathMatcher.match("/**/clients/**", request.getRequestURI())) {
                            return true;
                        }

                        if (antPathMatcher.match("/**/services/**", request.getRequestURI())) {
                            return true;
                        }
                        if (antPathMatcher.match("/**/redis/**", request.getRequestURI())) {
                            return true;
                        }
                        
                        return false;
                    }
                }

        ).authorizeRequests().antMatchers(permitUrlProperties.getIgnored()).permitAll()
        .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
        .anyRequest()
                .authenticated();
    }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值