引言
在Spring Cloud需要使用oauth2来实现多个微服务的统一认证授权,通过向OAuth服务发送某个类型的grant type进行集中认证和授权,从而获得access_token,而这个token是受其他微服务信任的,我们在后续的访问可以通过access_token来进行,从而实现了微服务的统一认证授权。
客户端根据约定的ClientID、ClientSecret、Scope来从Access Token URL地址获取AccessToken,并经过AuthURL认证,用得到的AccessToken来访问其他资源接口。
Spring Cloud OAuth2 需要依赖Spring Security。
OAuth2角色划分:
- 「Resource Server」:被授权访问的资源
- 「Authotization Server」:OAuth2认证授权中心
- 「Resource Owner」: 用户
- 「Client」:使用API的客户端(如Android 、IOS、web app)
OAuth2四种授权方式:
- 授权码模式(authorization code):用在客户端与服务端应用之间授权
- 简化模式(implicit):用在移动app或者web app(这些app是在用户的设备上的,如在手机上调起微信来进行认证授权)
- 密码模式(resource owner password credentials):应用直接都是受信任的(都是由一家公司开发的)
- 客户端模式(client credentials):用在应用API访问
2. OAuth2 环境搭建
2.1 认证授权中心服务
2.1.1 密码模式
1.添加maven依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- springboot整合freemarker --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!-->spring-boot 整合security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- spring-cloud-starter-oauth2 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> </dependencies> <!-- 注意: 这里必须要添加, 否者各种依赖有问题 --> <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.创建授权配置信息
// 配置授权中心信息 @Configuration @EnableAuthorizationServer // 开启认证授权中心 public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // accessToken有效期 private int accessTokenValiditySeconds = 7200; // 两小时 // 添加商户信息 public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // withClient appid clients.inMemory().withClient("client_1").secret(passwordEncoder().encode("123456")) .authorizedGrantTypes("password","client_credentials","refresh_token").scopes("all").accessTokenValiditySeconds(accessTokenValiditySeconds); } // 设置token类型 public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) { // 允许表单认证 oauthServer.allowFormAuthenticationForClients(); // 允许check_token访问 oauthServer.checkTokenAccess("permitAll()"); } @Bean AuthenticationManager authenticationManager() { AuthenticationManager authenticationManager = new AuthenticationManager() { public Authentication authenticate(Authentication authentication) throws AuthenticationException { return daoAuhthenticationProvider().authenticate(authentication); } }; return authenticationManager; } @Bean public AuthenticationProvider daoAuhthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService()); daoAuthenticationProvider.setHideUserNotFoundExceptions(false); daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return daoAuthenticationProvider; } // 设置添加用户信息,正常应该从数据库中读取 @Bean UserDetailsService userDetailsService() { InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(); userDetailsService.createUser(User.withUsername("user_1").password(passwordEncoder().encode("123456")) .authorities("ROLE_USER").build()); userDetailsService.createUser(User.withUsername("user_2").password(passwordEncoder().encode("1234567")) .authorities("ROLE_USER").build()); return userDetailsService; } @Bean PasswordEncoder passwordEncoder() { // 加密方式 PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); return passwordEncoder; } }
3.启动授权服务
@SpringBootApplication public class AppOauth2 { public static void main(String[] args) { SpringApplication.run(AppOauth2.class, args); } }
4.获取accessToken请求地址: http://localhost:8080/oauth/token
5.验证accessToken是否有效:http://localhost:8080/oauth/check_token?token=b212eaec-63a7-489d-b5a2-883ec248c417
6.刷新新的accessToken:http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=4803dbfe-41c8-417c-834e-6be6b296b767&client_id=client_1&client_secret=123456,需要配置:
public void configure(AuthorizationServerEndpointsConfigurer endpoints) { endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); endpoints.authenticationManager(authenticationManager()); endpoints.userDetailsService(userDetailsService()); }
2.1.2 授权模式
1.新增授权权限
public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // withClient appid clients.inMemory().withClient("client_1").secret(passwordEncoder().encode("123456")) .authorizedGrantTypes("password", "client_credentials", "refresh_token", "authorization_code") .scopes("all").redirectUris("http://www.xxx.com") .accessTokenValiditySeconds(accessTokenValiditySeconds) .refreshTokenValiditySeconds(refreshTokenValiditySeconds); }
User must be authenticated with Spring Security before authorization can be completed.
3.解决办法 添加Security权限
@Component public class SecurityConfig extends WebSecurityConfigurerAdapter { // 授权中心管理器 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { AuthenticationManager manager = super.authenticationManagerBean(); return manager; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 拦截所有请求,使用httpBasic方式登陆 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic(); } }
2.2 资源服务端
一个资源服务器,各个服务之间的通信(访问需要权限的资源)时需携带访问令牌
资源服务器通过 @EnableResourceServer 注解来开启一个 OAuth2AuthenticationProcessingFilter 类型的过滤器,通过继承 ResourceServerConfigurerAdapter 类来配置资源服务器。
1.添加maven依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> </parent> <!-- 管理依赖 --> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- SpringBoot整合Web组件 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- springboot整合freemarker --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!-->spring-boot 整合security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> </dependencies> <!-- 注意: 这里必须要添加, 否者各种依赖有问题 --> <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.application.yml
server: port: 8081 logging: level: org.springframework.security: DEBUG security: oauth2: resource: ####从认证授权中心上验证token tokenInfoUri: http://localhost:8080/oauth/check_token preferTokenInfo: true client: accessTokenUri: http://localhost:8080/oauth/token userAuthorizationUri: http://localhost:8080/oauth/authorize ###appid clientId: client_1 ###appSecret clientSecret: 123456
3.资源拦截配置
@Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { // 对 api/order 请求进行拦截 http.authorizeRequests().antMatchers("/api/order/**").authenticated(); } }
4.资源服务请求
@RestController @RequestMapping("/api/order") public class OrderController { @RequestMapping("/addOrder") public String addOrder() { return "addOrder"; } }
5.启动权限
@SpringBootApplication @EnableOAuth2Sso public class AppOrder { public static void main(String[] args) { SpringApplication.run(AppOrder.class, args); } }
6.资源访问,请求资源: http://127.0.0.1:8081/api/order/addOrder
Authorization: bearer 31820c84-2e52-408f-9d21-a62483aad59d
3. 将应用信息改为数据库存储
1.添加maven依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
2.application.yml
spring: datasource: hikari: connection-test-query: SELECT 1 minimum-idle: 1 maximum-pool-size: 5 pool-name: dbcp1 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/alan-oauth?autoReconnect=true&useSSL=false username: root password: 123456
3.修改配置文件类
// 配置授权中心信息 @Configuration @EnableAuthorizationServer // 开启认证授权中心 public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; @Autowired @Qualifier("dataSource") private DataSource dataSource; // @Autowired // private UserDetailsService userDetailsService; @Bean public TokenStore tokenStore() { // return new InMemoryTokenStore(); //使用内存中的 token store return new JdbcTokenStore(dataSource); /// 使用Jdbctoken store } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 添加授权用户 clients.jdbc(dataSource); // .withClient("client_1").secret(new BCryptPasswordEncoder().encode("123456")) // .authorizedGrantTypes("password", "refresh_token", "authorization_code")// 允许授权范围 // .redirectUris("http://www.xxx.com").authorities("ROLE_ADMIN", "ROLE_USER")// 客户端可以使用的权限 // .scopes("all").accessTokenValiditySeconds(7200).refreshTokenValiditySeconds(7200); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager) .userDetailsService(userDetailsService());// 必须设置 // UserDetailsService // 否则刷新token 时会报错 } @Bean UserDetailsService userDetailsService() { InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(); userDetailsService.createUser(User.withUsername("user_1").password(new BCryptPasswordEncoder().encode("123456")) .authorities("ROLE_USER").build()); userDetailsService.createUser(User.withUsername("user_2") .password(new BCryptPasswordEncoder().encode("1234567")).authorities("ROLE_USER").build()); return userDetailsService; } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()") .allowFormAuthenticationForClients();// 允许表单登录 } }