展开
拓展 关闭
订阅号推广码
GitHub
视频
公告栏 关闭

分布式认证授权(一)

  • 创建eureka子模块

  • pom

<dependencies>
    <dependency>
        <groupId>com.mengxuegu</groupId>
        <artifactId>mengxuegu-cloud-oauth2-base</artifactId>
        <version>${mengxuegu-security.version}</version>
    </dependency>
    <!-- 导入Eureka-server 服务端依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>
  • yml
server:
  port: 6001 # 服务端口
eureka:
  instance:
    hostname: localhost # eureka服务端的实例名称
  client:
    registerWithEureka: false # 服务注册,false表示不将自已注册到Eureka服务中
    fetchRegistry: false # 服务发现,false表示自己不从Eureka服务中获取注册信息
    serviceUrl:    # Eureka客户端与Eureka服务端的交互地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  • 启动类
@EnableEurekaServer // 标识它是Eureka服务器
@SpringBootApplication
public class EurekaServer_6001 {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServer_6001.class, args);
    }

}
  • 启动注册中心模块,打开浏览器测试

  • 将认证服务器和资源服务器注册到eureka服务注册中心

# 在认证服务器子模块中添加如下
# 导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

# 启动类添加注解
@EnableEurekaClient

# 配置yml
eureka:
  client:
    registerWithEureka: true # 服务注册开关
    fetchRegistry: true # 服务发现开关
    serviceUrl: # 注册到哪一个Eureka Server服务注册中心,多个中间用逗号分隔
      defaultZone: http://localhost:6001/eureka
  instance:
    instanceId: ${spring.application.name}:${server.port} # 指定实例ID,页面会显示主机名
    preferIpAddress: true     #访问路径可以显示IP地址
spring:
  application:
    name: auth-server # Eureka页面显示

# 重启认证服务器器子模块

# 在资源服务器子模块中添加如下
# 导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

# 启动类添加注解
@EnableEurekaClient

# yml配置
eureka:
  client:
    registerWithEureka: true # 服务注册开关
    fetchRegistry: true # 服务发现开关
    serviceUrl: # 注册到哪一个Eureka Server服务注册中心,多个中间用逗号分隔
      defaultZone: http://localhost:6001/eureka
  instance:
    instanceId: ${spring.application.name}:${server.port} # 指定实例ID,页面会显示主机名
    preferIpAddress: true     #访问路径可以显示IP地址

spring:
  application:
    name: product-server   # Eureka页面显示

# 启动资源服务器子模块
# pom
<dependencies>
    <dependency>
        <groupId>com.mengxuegu</groupId>
        <artifactId>mengxuegu-cloud-oauth2-base</artifactId>
        <version>${mengxuegu-security.version}</version>
    </dependency>
    <!-- 一样作为资源服务器,所以要引入 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--zuul路由网关依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
</dependencies>

# yml
server:
  port: 7001 # 端口号
spring:
  application:
    name: zuul-gateway
eureka:
  client:
    registerWithEureka: true # 服务注册开关
    fetchRegistry: true # 服务发现开关
    serviceUrl: # 注册到哪一个Eureka Server服务注册中心,多个中间用逗号分隔
      defaultZone: http://localhost:6001/eureka
  instance:
    instanceId: ${spring.application.name}:${server.port} # 指定实例ID,页面会显示主机名
    preferIpAddress: true     #访问路径可以显示IP地址
zuul: # 网关配置
  sensitive-headers: null # 默认Zuul认为请求头中 "Cookie", "Set-Cookie", "Authorization" 是敏感信息,它不会转发请求,因为把它设置为空,就会转发了
  add-host-header: true # 正确的处理重定向操作
  routes:
    authentication: # 路由名称,名称任意,保持所有路由名称唯一
      path: /auth/** # 访问路径,转发到 auth-server 服务处理
      serviceId: auth-server # 指定服务ID,会自动从Eureka中找到此服务的ip和端口
      stripPrefix: false # 代理转发时去掉前缀,false:代理转发时不去掉前缀 例如:为true时请求 /product/get/1,代理转发到/get/1
    product:   # 商品服务路由配置
      path: /product/** # 转发到 product-server 服务处理
      serviceId: product-server
      stripPrefix: false

# 启动类
@EnableZuulProxy //开启zuul的功能
@EnableEurekaClient
@SpringBootApplication
public class ZuulServer_7001 {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServer_7001.class, args);
    }

}
  • 启动网关模块,查看注册中心

  • jwt令牌管理和配置网关的资源配置类

  • 认证服务器和资源服务器相对于网关来说,就是资源;这里将网关看成认证服务器和资源服务器的资源服务器,当请求经过网关的时候,使用公钥解密,复制public.txt到resource目录下

  • 加载公钥解密

@Configuration
public class TokenConfig {

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 非对称加密,资源服务器使用公钥解密 public.txt
        ClassPathResource resource = new ClassPathResource("public.txt");
        String publicKey = null;
        try {
            publicKey = IOUtils.toString(resource.getInputStream(), "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        converter.setVerifierKey(publicKey);
        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        // Jwt管理令牌
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

}
  • 配置资源的访问权限(即通过网关转发到认证服务器时设置为不需要权限,通过网关访问资源服务器时需要PRODUCT_API权限)
@Configuration
public class ResourceServerConfig {

    public static final String RESOURCE_ID = "product-server";

    @Autowired
    private TokenStore tokenStore;

    // 认证服务资源
    @Configuration
    @EnableResourceServer
    public class AuthResourceServerConfig extends ResourceServerConfigurerAdapter{
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId(RESOURCE_ID)
                    .tokenStore(tokenStore)
            ;
        }
        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 关于请求认证服务器资源,则所有请求放行
            http.authorizeRequests()
                    .anyRequest().permitAll();
        }
    }

    // 商品资源服务器的资源
    @Configuration
    @EnableResourceServer
    public class ProductResourceServerConfig extends ResourceServerConfigurerAdapter{
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId(RESOURCE_ID)
                    .tokenStore(tokenStore)
            ;
        }
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/product/**")
                    .access("#oauth2.hasScope('PRODUCT_API')");
        }
    }

}
  • 编写security配置类,放行访问网关的所有请求
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 当前将所有请求放行,交给资源配置类进行资源权限判断
     * 因为默认情况下会拦截所有请求
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
    }
    
}
  • 自定义过滤器转发请求
将解析后的用户信息转发给目标微服务,这样目标微服务可以判断用户可访问的权限。
自定义过虑器需要继承 ZuulFilter,ZuulFilter是一个抽象类,需要覆盖它的4个方法,如下:
filterType:返回字符串代表过滤器的类型,返回值有:
pre:在请求路由之前执行
route:在请求路由时调用
post:请求路由之后调用, 也就是在route和errror过滤器之后调用
error:处理请求发生错误时调用
filterOrder:此方法返回整型数值,通过此数值来定义过滤器的执行顺序,数字越小优先级越高。
shouldFilter:返回Boolean值,判断该过滤器是否执行。返回true表示要执行此过虑器,false不执行。
run:过滤器的业务逻辑
  • 具体代码
/**
 * 请求资源前,先通过此 过滤器进行用户信息解析和校验 转发
 */
@Component
public class AuthenticationFilter extends ZuulFilter {

    Logger logger = LoggerFactory.getLogger(getClass());

    // 表示执行当前过滤器
    @Override
    public String filterType() {
        return "pre";
    }

    // 指定0,值越小,优先级越高
    @Override
    public int filterOrder() {
        return 0;
    }

    // 设置为true时,表示执行下面的run方法
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        Authentication authentication =
                SecurityContextHolder.getContext().getAuthentication();
        // 如果解析到令牌就会封装到OAuth2Authentication对象
        if( !(authentication instanceof OAuth2Authentication)) {
            return null;
        }
        logger.info("网关获取到认证对象:" + authentication);
        // 用户名,没有其他用户信息
        Object principal = authentication.getPrincipal();
        // 获取用户所拥有的权限
        Collection<? extends GrantedAuthority> authorities
                = authentication.getAuthorities();
        // 将权限放到set集合
        Set<String> authoritySet = AuthorityUtils.authorityListToSet(authorities);
        // 获取请求描述
        Object details = authentication.getDetails();
        // 将以上信息放到map集合
        Map<String, Object> result =  new HashMap<>();
        result.put("principal", principal);
        result.put("authorities", authoritySet);
        result.put("details", details);
        // 获取当前请求上下文
        RequestContext context = RequestContext.getCurrentContext();
        // 将用户信息和权限信息转成json,再通过base64进行编码
        String base64 = Base64Utils.encodeToString(JSON.toJSONString(result).getBytes());
        // 添加到请求头,传给下游服务
        context.addZuulRequestHeader("auth-token", base64);
        return null;
    }

}
  • 解决跨域
@Configuration
public class GatewayConfig {

    // 配置全局解决zuul服务中的cors跨域问题
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedMethod("*");
        //↓核心代码
        corsConfiguration.addExposedHeader("Authorization");
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }

}
posted @ 2022-06-23 15:09  DogLeftover  阅读(48)  评论(0编辑  收藏  举报