用Oauth2.0 管理微服务中的开放接口,对接口进行授权认证:
在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 在引入Oauth框架是需要引入Spring security
所以别人在调用我们接口的时候,首先需要在我们平台上申请appId和appKey,这里我们需要搭建一个门户网站供别人使用,实例里面就直接写死了。
微服务安全框架 SpringBootSecurity,Oauth2角色划分
1、Resource Server:被授权访问的资源
2、Authotization Server:OAUTH2认证授权中心
3、Resource Owner: 用户
4、Client:使用API的客户端(如Android 、IOS、web app)
OAuth2四种授权方式:
1、授权码(认证码)模式(Authorization code):用在客户端与服务器端应用之间的授权,response_type=code
2、简化(隐形)模式(Impilict):应用在移动app或者web app(在移动设备上的,如在手机中调用微信来进行认证授权),response_type=token
3、密码模式(Resource Owner Password Credentials):应用直接都是受信的,如都是由同一家公司开发,grant_type=password
4、客户端模式(Client Credentials):用在应用API访问,grant_type=client_credential
一般情况下,1和3应用比较多,2都是应用在移动端,4应用不多
客户端不能直接调用订单服务的,需要走网关,然后进入到订单服务里面。后期服务比较多时候,需要授权认证。
添加一层 Oauth2.0授权中心 ,对于订单服务、商品服务 都要做accessToken验证的 。 通过网关之后还有一层认证授权中心。
客户端先去获取accessToken,而accessToken生成于Oauth2.0认证授权中心。即为: 客户端到认证授权中心,通过appId和appSerect拿到accessToken。
验证accessToken是否有效等等也是通过它实现的。
认证授权中心是管理accessToken相关的
过程:客户端携带appid和授权码去Oauth2.0认证授权中心,获取accessToken。
客户端携带accessToken去访问,经过网关,网关转发到订单服务,然后去Oauth2.0认证授权中心去验证accessToken。
要先搭建认证授权中心,然后在搭建资源服务器
maven依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.1.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.M7version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
spring-boot 整合security -->
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
dependencies>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/libs-milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
配置授权中心:AuthorizationServerConfig
@Configuration
@EnableAuthorizationServer // 开启认证授权中心
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//accessToken 有效期2小时
private static final int ACCESSTOKENVALIDITYSECONDS = 7200;
//刷新accessToken
private static final int REFRESHTOKENVALIDITYSECONDS = 7200;
/**
* 添加商户信息
* 配置appid、appKey、回调地址、token有限期
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//申请获取到appid和appKey 暂时写死
clients.inMemory().withClient("client_1").secret(passwordEncoder().encode("123456"))
//重定向地址 授权类型 可进行授权的列表
.redirectUris("http://www.baidu.com").authorizedGrantTypes("password", "client_credentials", "refresh_token", "authorization_code").scopes("all")
//accessToken有效期
.accessTokenValiditySeconds(ACCESSTOKENVALIDITYSECONDS)
//刷新accessToken
.refreshTokenValiditySeconds(REFRESHTOKENVALIDITYSECONDS);
}
// 设置token类型
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET,
HttpMethod.POST);
//刷新令牌接口
endpoints.authenticationManager(authenticationManager());
endpoints.userDetailsService(userDetailsService());
}
@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;
}
}
因为依赖于security框架!必须要配置security。强制要求配置,不然访问授权中心会直接报错
@Component
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// 拦截所有请求,使用httpBasic方式登陆
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().formLogin();
}
}
可以看到这里appId、appKey、重定向地址都是写死的,应该都是放在数据库的
启动类:
@SpringBootApplication
public class AppOauth2Server {
public static void main(String[] args) {
SpringApplication.run(AppOauth2Server.class, args);
}
}
a.使用授权码code获取accessToken
访问 http://localhost:8080/oauth/authorize?response_type=code&client_id=client_1&client_secret=123456&redirect_uri=http://www.baidu.com
点击登录:如下图所示,应用client_1 是否接受资源授权? 接受 和 拒绝
选择approve,点击授权按钮
跳转到了百度页面,连接后面跟的code的值就是授权码
使用授权码获取accessToken:
http://localhost:8080/oauth/token?grant_type=authorization_code&code=Nu6Uxk&redirect_uri=http://www.baidu.com&scope=all&client_id=client_1&client_secret=123456
还可以验证token是否有效
http://localhost:8080/oauth/check_token?token=514ec7e6-b5cd-462a-a3db-2f6b8548df37
也可以调用该接口对token进行刷新
http://localhost:8080/oauth/token?grant_type=refresh_token&refresh_token=9da9bac7-27d9-45ad-9e77-7db2b05ce183&client_id=client_1&client_secret=123456
// 设置token类型
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager()).allowedTokenEndpointRequestMethods(HttpMethod.GET,
HttpMethod.POST);
//重新设置userDetailsService 不然刷新accessToken时候会报错!!!
endpoints.authenticationManager(authenticationManager());
endpoints.userDetailsService(userDetailsService());
}
b.使用密码方式获取accessToken
http://localhost:8080/oauth/token?grant_type=password&username=user_1&password=123456&client_id=client_1&client_secret=123456&scope=all
可以看到,与刚才通过授权码获取到的accessToken是一样的。因为是否appId进行关联的。
小结: 密码模式想要获取到accessToken,不用authorization code . 使用用户名和密码,就可以获取到accessToken了。
用户名 和 密码要与配置的一致,实际项目时候用的是读数据库动态获取。
maven依赖:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.1.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.M7version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
spring-boot 整合security -->
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-oauth2artifactId>
dependency>
dependencies>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/libs-milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
资源拦截配置config:
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
//对 /api/order请求进行拦截
http.authorizeRequests().antMatchers("/api/order/**").authenticated();
}
}
资源配置controller:
@RestController
public class OrderController {
@RequestMapping("/api/order/addOrder")
public String addOrder() {
return "addOrder";
}
@RequestMapping("/orderInfo")
public String orderInfo(){
return "orderInfo";
}
}
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
启动类:
@SpringBootApplication
@EnableOAuth2Sso
public class AppOrder {
public static void main(String[] args) {
SpringApplication.run(AppOrder.class, args);
}
}
拦截器会拦截以/api/order开头的请求:
当我们访问 http://localhost:8081/orderInfo 连接时,不需要授权
访问需要授权的接口:http://localhost:8081/api/order/addOrder
这时就要使用我们前面获取的accessToken进行访问,在请求头中添加如下参数就可以正常访问了:
注:正常情况下拦截服务资源 是在网关里面的!
拦截资源统一由网关进行Oauth2.0验证
开放接口和内部接口一定要独立出来! 可以封装业务逻辑相同,但是内部外部一定要相同
public开头的 需要做Oauth2.0验证的
官方推荐SQL:
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
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(25) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256)
);
create table oauth_access_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(250) 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 TIMESTAMP,
lastModifiedAt TIMESTAMP
);
-- customized oauth_client_details table
create table ClientDetails (
appId VARCHAR(256) PRIMARY KEY,
resourceIds VARCHAR(256),
appSecret VARCHAR(256),
scope VARCHAR(256),
grantTypes VARCHAR(256),
redirectUrl VARCHAR(256),
authorities VARCHAR(256),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additionalInformation VARCHAR(4096),
autoApproveScopes VARCHAR(256)
);
注:表名和字段名不要随便改。
在认证授权服务器中增加如下pom文件依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
增加配置文件信息: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/oauth2?autoReconnect=true&useSSL=false
username: root
password: 123456
配置认证授权:AuthorizationServerConfigByDataSource
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfigByDataSource extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManager")
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.baidu.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 时会报错
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();// 允许表单登录
}
@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;
}
}