spring cloud oauth2中的Zuul

网关有以下几个作用:

  • 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
  • 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
  • 动态路由:动态的将请求路由到不同的后端集群中。
  • 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。

在oauth2网关除了以上作用 还有鉴权的作用 如果token无效 则直接返回

GetwayApplication 

@EnableZuulProxy说明为zuul网关服务

restTemplate配置开启服务间调用

@EnableEurekaClient //标识是eureka客户端
@SpringBootApplication
@EnableZuulProxy
public class GetwayApplication {

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

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder.build();
    }
}

GatewayConfig 

解决跨域问题

@Configuration
public class GatewayConfig {

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

SpringSecurityConfig 

Spring security核心配置 这里配置为开放所有权限

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 当前将所有请求放行,交给资源配置类进行资源权限判断
     * 因为默认情况下会拦截所有请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
    }
}

TokenConfig 

token的保存方式 不多说

@Configuration
public class TokenConfig {

    /**
     * 使用redis存储
     */
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
}

ResourceServerConfig 

资源服务器的核心配置 说白了网关也作为一个资源提供给第三方访问  也就是说 在访问时会判断token是否有权限访问 getway资源 所以在 oauth_client_details表resource_ids字段中 应该有以下配置 对应resources.resourceId(RESOURCE_ID)

@Configuration
// 标识为资源服务器,请求服务中的资源,就要带着token过来,找不到token或token是无效访问不了资源
@EnableResourceServer
// 开启方法级别权限控制
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig {

    public static final String RESOURCE_ID = "getway";

    /**
     * 异常返回json
     */
    @Autowired
    private AuthExceptionEntryPointConfig authExceptionEntryPointConfig;


    /**
     * 上文有不多说
     */
    @Autowired
    private TokenStore tokenStore;

    /**
     * 认证服务资源
     */
    @Configuration
    @EnableResourceServer
    public class AuthResourceServerConfig extends ResourceServerConfigurerAdapter {
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId(RESOURCE_ID) //验证是否有getway权限 在oauth_client_details中配置
                .tokenStore(tokenStore)//验证token保存方式与验证方式
                .authenticationEntryPoint(authExceptionEntryPointConfig) //token验证失败返回json
                .stateless(true);// 会话机制stateless开启
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // 关于请求认证服务器资源,则所有请求放行
            http.authorizeRequests().anyRequest().permitAll();
        }

        //加密密码 不多说
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
}

application.yml

这时候小伙伴问了 那么怎么验证token是否有效的呢

认证中心中提供一个接口/oauth/check_token 在认证服务器中AuthorizationServerConfig配置的就是他!!!!

这个接口地址等的配置 在application.yml中 

同时填写接入的第三方客户端的信息(这个填写访问user资源的前端信息即可)  对应着oauth_client_details表的client_id与client_secret字段 (记住是解密后的密码) 

这样就可以调用了/oauth/check_token接口了

同时zuul的分发关系也在其中配置

server:
  port: 6001 # 端口号
spring:
  application:
    name: gateway
eureka:
  instance:
    hostname: ${spring.cloud.client.ip-address}
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${server.port}
  client:
    security:
      basic:
        user: admin
        password: xxxxx
    service-url:
      defaultZone: http://${eureka.client.security.basic.user}:${eureka.client.security.basic.password}@localhost:5001/eureka
zuul: # 网关配置
  host:
    connect-timeout-millis: 15000 #HTTP连接超时要比Hystrix的大
    socket-timeout-millis: 60000   #socket超时
  sensitive-headers: null # 默认Zuul认为请求头中 "Cookie", "Set-Cookie", "Authorization" 是敏感信息,它不会转发请求,因为把它设置为空,就会转发了
  add-host-header: true # 正确的处理重定向操作
  routes:
    authentication: # 路由名称,名称任意,保持所有路由名称唯一
      path: /user/** # 访问路径,转发到 auth-server 服务处理
      serviceId: user-server # 指定服务ID,会自动从Eureka中找到此服务的ip和端口
      stripPrefix: false # 代理转发时去掉前缀,false:代理转发时不去掉前缀 例如:为true时请求 /product/get/1,代理转发到/get/1
ribbon:
  ConnectTimeout: 10000 # 连接超时时间(ms)
  ReadTimeout: 10000 # 通信超时时间(ms)
hystrix:
  command:
    default:
    execution:
    isolation:
    thread:
    timeoutInMilliseconds: 60000 # 设置hystrix的超时时间为6000ms
#验证token是否正确
security:
  oauth2:
    client:
      client-id: user-vue
      client-secret: 1234
    resource:
      token-info-uri: http://localhost:8001/oauth/check_token
#前段访问地址
cors:
  ip: http://192.168.xx.xx:8080

AuthExceptionEntryPointConfig 

如果token无效 返回给前台json 

这里楼主有个问题 在测试跨域时 需要指定前端ip 否则还是无效 corsIp为前端ip地址

@Component
public class AuthExceptionEntryPointConfig implements AuthenticationEntryPoint {

    /**
     * 跨域前段ip
     */
    @Value("${cors.ip}")
    private String corsIp;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws ServletException {
        Throwable cause = authException.getCause();
        response.setStatus(HttpStatus.OK.value());
        response.addHeader("Access-Control-Allow-Origin", corsIp);
        response.addHeader("Access-Control-Allow-Methods", "POST,OPTIONS,PUT,HEAD");
        response.addHeader("Access-Control-Max-Age", "3600000");
        response.addHeader("Access-Control-Allow-Credentials", "true");
        response.addHeader("Access-Control-Allow-Headers",
            "Authentication,Origin, X-Requested-With, Content-Type, Accept,token");
        response.setHeader("Content-Type", "application/json;charset=UTF-8");
        Result result;
        try {
            if (cause instanceof InvalidTokenException) {
                result = new Result(402, "认证失败,无效或过期token");
            } else {
                result = new Result(401, "认证失败,没有携带token");
            }
            response.getWriter().write(new ObjectMapper().writeValueAsString(result));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

AuthenticationFilter 

将token解析后放入请求头 转发给需要调用的资源服务器

@Component
public class AuthenticationFilter extends ZuulFilter {
    Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @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<String> authoritySet = AuthorityUtils.authorityListToSet(authorities);
        // 请求详情
        Object details = authentication.getDetails();
        Map<String, Object> result = new HashMap<>(3);
        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("authentication-token", base64);
        return null;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_20143059/article/details/113759593