在上一篇博客基础上,我们继续搭建资源服务:https://blog.csdn.net/BiandanLoveyou/article/details/117593433
注意:
市面上很多教学课程和很多博客都是在代码里写固定用户名和密码,或者配置客户端的服务都是使用内存的形式 clients.inMemory(),因此很多问题都屏蔽掉了!
今天我们演示的是从数据库里读取数据,而不是写固定在代码里,因此参考价值很高!实际项目中,也可以换成 Redis 做数据源。这里为了方便学习,使用数据库了。
整体代码结构:
application.xml 配置(需要有数据库的配置,且与授权认证中心的属于同一套数据库。实际项目中,授权中心和资源服务应该分开部署):
server:
port: 8081
# 将SpringBoot项目作为单实例部署调试时,不需要注册到注册中心
eureka:
client:
fetch-registry: false
register-with-eureka: false
spring:
application:
name: order-server
# 数据源配置
datasource:
# 使用阿里巴巴的 druid 数据源
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/study_oauth2?characterEncoding=utf-8
username: root
password: root
# security 配置
security:
oauth2:
resource:
# 从认证授权中心上验证 token
tokenInfoUri: http://127.0.0.1:8080/oauth/check_token
preferTokenInfo: true
client:
accessTokenUri: http://127.0.0.1:8080/oauth/token
userAuthorizationUri: http://127.0.0.1:8080/oauth/authorize
clientId: good123
clientSecret: 123
ResourceServerConfiguration 代码:
package com.study.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
/**
* @author biandan
* @description
* @signature 让天下没有难写的代码
* @create 2021-06-06 下午 1:39
*/
@Configuration
@EnableResourceServer //开启资源服务中心
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// 对 api/order 请求进行拦截 验证 accessToken
http.authorizeRequests().antMatchers("/api/order/**").authenticated();
}
}
TokenStore 的配置,注意:默认使用内存的方式 clients.inMemory(),因为我们认证中心是使用数据库的方式,因此这里必须配置相同的认证方式。
package com.study.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
/**
* @author biandan
* @description
* @signature 让天下没有难写的代码
* @create 2021-06-05 下午 3:39
*/
@Configuration
public class TokenStoreConfig {
@Autowired
private DataSource dataSource;
//设置保存token的方式,一共有五种,这里采用数据库的方式
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
UserEntity 代码(参考认证中心的):
package com.study.entity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.List;
/**
* @author biandan
* @description 用户实体类,需要实现 UserDetails 接口
* @signature 让天下没有难写的代码
* @create 2021-05-31 下午 12:43
*/
public class UserEntity implements UserDetails {
private Integer id;
//用户名
private String username;
//密码
private String password;
//用户所有权限集合
private List<GrantedAuthority> authorities = new ArrayList<>();
//是否生效,1=true,0=false
private boolean enabled;
//是否未过期,1=true,0=false
private boolean accountNonExpired;
//是否未锁定,1=true,0=false
private boolean accountNonLocked;
//证书是否未过期,1=true,0=false
private boolean credentialsNonExpired;
省略 get、set 方法,请补上
}
注意:
这里必须要加上 UserEntity 这个实体类,且包的路径必须与认证中心的一致,这样才能反序列化。否则报错:
OrderController:
package com.study.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author biandan
* @description
* @signature 让天下没有难写的代码
* @create 2021-06-06 下午 1:40
*/
@RestController
@RequestMapping("/api/order")
public class OrderController {
private static final SimpleDateFormat SDf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@RequestMapping("/getInfo")
public String getInfo(){
return "现在是北京时间:" + SDf.format(new Date());
}
}
启动类:
package com.study;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author biandan
* @description
* @signature 让天下没有难写的代码
* @create 2021-06-06 下午 1:36
*/
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
测试:把有效的 access_token 放在 Headers 的 Authorization 上,VALUE 值的前面增加 bearer 和一个空格。
题外话:
TokenStore 的配置默认使用内存的方式 clients.inMemory(),我们可以找到 DefaultTokenServices.class,loadAuthentication 方法,打上断点跟踪。如果不设置其它的方式,默认使用内存方式,如图:
如果资源服务和认证中心的 TokenStore 方式不一致,会在校验 access_token 的时候一直报错 Invalid access token,虽然我们通过校验 token 是正确的,但是在内存里是找不到这个 token 的,存入数据库了。
{
"error": "invalid_token",
"error_description": "Invalid access token: ce14a553-60e3-4a7c-a719-5522b09d22a8"
}
因此我们增加了 TokenStoreConfig 类之后,就使用的是我们指定的 JdbcTokenStore 方式了。如图:
OK,基于 Oauth2 搭建微服务 API 开放平台讲解到这。
本篇博客代码(内附数据库表):https://pan.baidu.com/s/1A9JkphSV3WnHxm5Ut2C-FA 提取码:26d3