微服务技术系列教程(41)- SpringCloud -OAuth2搭建微服务开放平台
引言
在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);
}
2.请求http://localhost:8080/oauth/authorize?response_type=code&client_id=client_1&redirect_uri=http://www.xxx.com ,访问报错:
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();// 允许表单登录
}
}