网上有不少与此类型类似的文章,可是在我搭建网关集成认证功能时,要么参考的这些文章并不全,要么不是我想要的或者说是比较复杂。所以本篇将会搭建一个简易的网关服务,采用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(好像换人维护了)