Spring Cloud 基于OAth2协议与JWT搭建一个简易网关

    网上有不少与此类型类似的文章,可是在我搭建网关集成认证功能时,要么参考的这些文章并不全,要么不是我想要的或者说是比较复杂。所以本篇将会搭建一个简易的网关服务,采用oauth2协议的passowrd模式来进行身份认证,jwt做为token令牌。最终达到须要登陆获取AccessToken才能向网关申请服务资源的效果。本篇博文代码已托管至码云。java

    项目总体的目录结构以下git

    版本:Spring Cloud Edgware.SR3spring

              Spring Boot 1.5.12.RELEASEapi

    1.首先建立父工程安全

    pom.xml部分以下服务器

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring.cloud.version>Edgware.SR3</spring.cloud.version>
        <spring.boot.version>1.5.12.RELEASE</spring.boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    2.搭建Eureka服务治理session

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

    3.搭建一个示例资源服务app

    提供/test端点用于测试less

@RestController
@EnableEurekaClient
@SpringBootApplication
public class ResourceServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ResourceServiceApplication.class, args);
    }

    @GetMapping("/test")
    public String testAuthentication() {
        return "authentication permit";
    }
}

    4.搭建认证服务ide

    用到以下依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

    认证服务配置

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //使用内存存储
        clients.inMemory()
                //分配客户端帐号
                .withClient("client")
                .secret("client-secret")
                //支持的受权类型
                .authorizedGrantTypes("refresh_token", "password")
                .scopes("server")
                //token有效时长
                .accessTokenValiditySeconds(1200)
                //refreshToken有效时长
                .refreshTokenValiditySeconds(50000);
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        //设置签名密钥
        jwtAccessTokenConverter.setSigningKey("demo");
        return jwtAccessTokenConverter;
    }

    //使用JWT做为token
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(jwtTokenStore())
                .accessTokenConverter(jwtAccessTokenConverter())
                .reuseRefreshTokens(true)
                //配置以生效password模式
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }
}

    注意:这里须要配置authenticationManager不然没法支持password模式。当获取token时,出现以下响应。

{
        "error": "unsupported_grant_type",
        "error_description": "Unsupported grant type: password"
}

    安全服务配置

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }
}

    简单模拟用户校验服务

@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;

    //username:任意 password:123456
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User(username, passwordEncoder.encode("123456"), 
                        AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
    }
}

    application.yml

spring:
  application:
    name: auth-server
server:
  port: 4040
security:
  oauth2:
    resource:
      filter-order: 3
management:
  security:
    enabled: false
eureka:
  client:
    service-url:
      defaultZone: http://eureka:pwd@localhost:8761/eureka

    5.搭建服务网关

    资源服务配置

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                //去掉获取token会被拦截
                .antMatchers("/auth/**").permitAll()
                .anyRequest().authenticated();
    }
}

    注意:这里须要放行/auth/**不然网关会在获取token请求时就对其身份进行校验拦截

    添加网关过滤器

@Component
public class AuthFilter extends ZuulFilter {
    //拦截类型
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    //拦截顺序
    @Override
    public int filterOrder() {
        return SERVLET_DETECTION_FILTER_ORDER - 1;
    }

    //开启拦截
    @Override
    public boolean shouldFilter() {
        return true;
    }

    //拦截处理逻辑
    @Override
    public Object run() {
        //获取用户认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(authentication);
        return null;
    }
}

    application.yml

spring:
  application:
    name: api-gateway
server:
  port: 8040
eureka:
  client:
    service-url:
      defaultZone: http://eureka:pwd@localhost:8761/eureka
zuul:
  ignoredServices: '*'
  routes:
    resource-service: /demo/**
    auth-server: /auth/**
  sensitive-headers:

management:
  security:
    enabled: false

security:
  oauth2:
    client:
      //在认证服务器中配置的客户端帐号
      clientId: client
      clientSecret: client-secret
    resource:
      token-info-uri: http://localhost:4040/oauth/check_token
      prefer-token-info: true
      jwt:
        key-uri: http://localhost:4040/oauth/token_key
  sessions: stateless

    注意:因为使用token-info-uri,所以需先启动auth-server再启动网关服务,由于网关服务在启动时会调用该端点获取数据。如先启动网关会出现以下报错日志。

I/O error on GET request for "http://localhost:4040/oauth/token_key": Connection refused: connect;
nested exception is java.net.ConnectException: Connection refused: connect

    至此,一个简单的网关认证服务就搭建完成了。接下来,让咱们来测试一下。

    直接经过网关代理请求测试端点,返回结果以下,响应提示须要认证后才可访问资源。

    password模式获取jwt token

    经过获取到的access_token请求资源服务

    控制台可见以下输出,可见在过滤器获取到了用户的身份信息

org.springframework.security.oauth2.provider.OAuth2Authentication@407c5f9d: Principal: user;
Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1,
tokenType=BearertokenValue=<TOKEN>; Granted Authorities: ROLE_USER

    本篇博文从PIG项目中简化剥离出来,若有须要进一步拓展的小伙伴们,推荐两个开源项目

    冷冷——PIG

    老A——AG-Admin(好像换人维护了)

相关文章
相关标签/搜索