发布于 2年前

SpringCloud配置网关安全

create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);

create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);

create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication BLOB
);

create table oauth_code (
  code VARCHAR(256), authentication BLOB
);

create table oauth_approvals (
    userId VARCHAR(256),
    clientId VARCHAR(256),
    scope VARCHAR(256),
    status VARCHAR(10),
    expiresAt DATETIME,
    lastModifiedAt DATETIME
);

 </div><div class="highlight"> ```
<pre class="highlight">```
@Configuration
// 设置当前服务器用来作为授权服务
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    // 配置的认证控制器,自定义认证的流程,比如获取到用户名,查询数据库然后比较验证
    @Autowired
    private AuthenticationManager authenticationManager;

    // 配置的数据库信息
    @Autowired
    private DataSource dataSource;

    // 设置存储token为mysql数据库,默认存在内存中
    // 也可设置为Redis
    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .tokenStore(tokenStore())
            .authenticationManager(authenticationManager);
    }
    // 配置客户端应用信息
    // 使用数据mysql数据库存储
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    // 设置服务的策略为认证通过才能继续
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("isAuthenticated()");
    }

}

 </div><div class="highlight"> ```
<pre class="highlight">```
@Configuration
@EnableWebSecurity
public class OAuth2WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 自定义的认证类,返回一个UserDetails对象
    @Autowired
    private UserDetailsService userDetailsService;

    // 密码转换
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 继承 WebSecurityConfigurerAdapter 的验证方式
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }

    // 继承 WebSecurityConfigurerAdapter 的验证方式
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

 </div><div class="highlight"> ```
<pre class="highlight">```
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    // UserDetailsService的方法
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这儿写死了,应该调用DB查询相关信息,然后返回对应的用户信息
        return User.withUsername(username)
                    .password(passwordEncoder.encode("12345"))
                    .authorities("ROLE_ADMIN")
                    .build();
    }

}

 </div><div class="highlight"> ```
<pre class="highlight">```
@Configuration
// 声明为资源服务器
@EnableResourceServer
public class Oauth2ResourceServiceConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        // 设置当前资源服务器的名称
        resources.resourceId("order-server");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
        // 这里可以自定义配置路由规则
        // http.authorizeRequests().antMatchers("/haha").permitAll();

    }
}

 </div><div class="highlight"> ```
<pre class="highlight">```
@EnableWebSecurity
@Configuration
public class Oauth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public ResourceServerTokenServices tokenServices(){
        RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId("orderService");
        tokenServices.setClientSecret("12345");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:9090/oauth/check_token");

        return tokenServices;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        OAuth2AuthenticationManager auth2AuthenticationManager = new OAuth2AuthenticationManager();
        auth2AuthenticationManager.setTokenServices(tokenServices());
        return auth2AuthenticationManager;
    }
}

 </div><div class="highlight"> ```
<pre class="highlight">```
引入zuul相关包
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

配置对应yaml文件

zuul:
  routes:
    token:
      url: http://localhost:9090
    order:
      url: http://localhost:9080
server:
    port: 9079

这样每次访问9070/token的接口都会转发到对应认证服务

 </div><div class="highlight"> ```
<pre class="highlight">```
@Slf4j
@Component
public class OAuthFilter extends ZuulFilter {

    private RestTemplate restTemplate = new RestTemplate();

    // 判断过滤器是不是起作用
    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {

        log.info("oauth start");

        // 获取请求对象
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        // 发往token服务器的请求,直接过去,因为现在还没有token
        if(StringUtils.startsWith(request.getRequestURI(), "/token")) {
            return null;
        }

        // 不是发往认证服务器的,就需要校验token
        String authHeader = request.getHeader("Authorization");
        // 没有认证信息 也继续往下走
        if(StringUtils.isBlank(authHeader)) {
            return null;
        }

        // 判断是否是Bearer认证
        if(!StringUtils.startsWithIgnoreCase(authHeader, "bearer ")) {
            return null;
        }

        try {
            // 获取token中的相关信息
            TokenInfo info = getTokenInfo(authHeader);
            request.setAttribute("tokenInfo", info);

        } catch (Exception e) {
            log.error("get token info fail", e);
        }

        return null;
    }

    // 发送请求到认证服务器,验证获取相关token信息
    // 这里的配置信息可以注入其他Bean来实现
    // 也可用consul等配置中心配置
    private TokenInfo getTokenInfo(String authHeader) {

        String token = StringUtils.substringAfter(authHeader, "Bearer ");
        String oauthServiceUrl = "http://localhost:9090/oauth/check_token";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setBasicAuth("gateway", "12345");

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("token", token);

        HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);

        ResponseEntity<TokenInfo> response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, TokenInfo.class);

        log.info("token info :" + response.getBody().toString());

        return response.getBody();
    }

    // 过滤器类型 有四种 pre,post,error,route
    // pre: 业务逻辑执行之前,执行run里的逻辑
    // post : 执行之后...
    // error : 报错之后,执行...
    @Override
    public String filterType() {
        return "pre";
    }

    // 过滤器执行顺序
    @Override
    public int filterOrder() {
        return 1;
    }

}

 </div><div class="highlight"> ```
<pre class="highlight">```
@Slf4j
@Component
public class AuditLogFilter extends ZuulFilter {

    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 业务处理插入相关日志信息
    @Override
    public Object run() throws ZuulException {
        log.info("audit log insert");
        return null;
    }

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

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

}

 </div><div class="highlight"> ```
<pre class="highlight">```
@Slf4j
@Component
public class AuthorizationFilter extends ZuulFilter {

    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 主要是将认证服务返回的token信息进行权限认证
    // 没有对应权限的话就调用 requestContext.setSendZuulResponse(false);
    // 设置zull网关不通过
    // 有对应权限就放开,发送到具体的地方
    @Override
    public Object run() throws ZuulException {

        log.info("authorization start");

        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        if(isNeedAuth(request)) {

            TokenInfo tokenInfo = (TokenInfo)request.getAttribute("tokenInfo");

            if(tokenInfo != null && tokenInfo.isActive()) {
                if(!hasPermission(tokenInfo, request)) {
                    log.info("audit log update fail 403");
                    handleError(403, requestContext);
                }

                requestContext.addZuulRequestHeader("username", tokenInfo.getUser_name());
            }else {
                // 去获取token的请求不做校验
                if(!StringUtils.startsWith(request.getRequestURI(), "/token")) {
                    log.info("audit log update fail 401");
                    handleError(401, requestContext);
                }
            }
        }

        return null;
    }

    private void handleError(int status, RequestContext requestContext) {
        requestContext.getResponse().setContentType("application/json");
        requestContext.setResponseStatusCode(status);
        requestContext.setResponseBody("{"message":"auth fail"}");
        // 调用这句话就不会向下执行了
        requestContext.setSendZuulResponse(false);
    }

    private boolean hasPermission(TokenInfo tokenInfo, HttpServletRequest request) {
        return true; //RandomUtils.nextInt() % 2 == 0;
    }

    private boolean isNeedAuth(HttpServletRequest request) {
        return true;
    }

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

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

}

 </div><div class="highlight"> ```
<pre class="highlight">```
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.2.4.RELEASE</version>
</dependency>

 </div><div class="highlight"> ```
<pre class="highlight">```
zuul:
  routes:
    token:
      url: http://localhost:9090
    order:
      url: http://localhost:9080
  # 默认不会转发请求头包含Authorization 、cookie的服务
  # 这儿设置为空,都会转发
  sensitive-headers:
  ratelimit:
    enabled: true
    # 这里可以设置存储的方式
    repository: JPA
    default-policy-list:
    # 3秒中最多2个请求 限制请求数
    - limit: 2
      # 2个请求的处理时间加起来不能超过1s,在请求数量少,但处理时间长的情景中使用
      quota: 1
      refresh-interval: 3
      # 根据url、请求方式、ip去限制流量
      type:
        - url
        - httpmethod
        #- origin

 </div><div class="highlight"> ```
<pre class="highlight">```
@Component
public class MyKeyGen extends DefaultRateLimitKeyGenerator {

    public MyKeyGen(RateLimitProperties properties,
            RateLimitUtils rateLimitUtils) {
        super(properties, rateLimitUtils);
    }

    @Override
    public String key(HttpServletRequest request, Route route, Policy policy) {
        // TODO Auto-generated method stub
        return super.key(request, route, policy);
    }
}

 </div><div class="highlight"> ```
<pre class="highlight">```
@Component
public class MyRateLimitErrorHandler extends DefaultRateLimiterErrorHandler {

    @Override
    public void handleError(String msg, Exception e) {
        super.handleError(msg, e);
    }
}
©2020 edoou.com   京ICP备16001874号-3