1. Введение
В предыдущей главе были представлены базовые теоретические знания о Spring Security Oauth2 и сценариях его использования.Аннотации в этой главе описывают, как реализовать унифицированную аутентификацию и авторизацию микросервисов через Oauth2 в Spring Cloud.
1.1 Решения
В этой статье в основном используются следующие решения:
- На основе схемы аутентификации токена без сохранения состояния (jwt) серверу не нужно сохранять статус входа пользователя;
- Основан на фреймворке безопасности spring + протокол oauth2;
Зачем использовать метод jwt? Чтобы избежать необходимости удаленно вызывать службу проверки подлинности и авторизации для каждого запроса, служба проверки подлинности и авторизации проверяет только один раз и возвращает JWT. Возвращенный JWT содержит всю информацию о пользователе, включая информацию о разрешениях.
1.2 Архитектура проектирования случая
Три проекта:
- eureka-server: центр регистрации, порт 8888. (Он был построен в предыдущих статьях, и в этой статье процесс построения не демонстрируется)
- auth-server: отвечает за авторизацию. Авторизация требует, чтобы пользователь предоставил clientId и пароль клиента, а также имя пользователя и пароль авторизованного пользователя. После правильной подготовки информации служба auth-service возвращает JWT, который содержит основную информацию о пользователе и информацию о точке авторизации и зашифрован с помощью RSA.
- auth-client: клиент аутентификации, общедоступная зависимость. Представлены все остальные службы ресурсов
- пользователь-сервер: как служба ресурсов, ее ресурсы защищены и требуют соответствующих разрешений для доступа. После того, как служба пользователь-сервер получает запрошенный пользователем JWT, она сначала расшифровывает JWT с помощью открытого ключа, получает информацию о пользователе и информацию о разрешениях пользователя, соответствующую JWT, а затем определяет, есть ли у пользователя разрешение на доступ к ресурсу.
- сервер заказа: такой же, как указано выше
Схема инженерной архитектуры:
Инженерные зависимости:
2. Создайте проект сервера авторизации
2.1 Добавить зависимости maven
Создайте новый модуль auth-server и добавьте следующие зависимости:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-oauth2</artifactId>
<groupId>com.hxmec</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2 Создайте таблицу данных
Таблицы, необходимые для проекта, в основном включают следующее:
Он в основном используется для хранения информации о клиенте, выданного токена доступа, токена обновления и другой информации в протоколе oauth2. ссылка на описание связанной таблицы oauth2Andrea.com/spring-OA UT…. В демонстрационном проекте токен доступа хранится в виде jwt, поэтому фактически используются только таблица oauth_client_details и таблица с информацией о пользователе sys_user. Если вместо этого вы используете хранилище jdbc, вам нужно использовать другие таблицы.
Оператор создания таблицы выглядит следующим образом:
-- 客户端应用注册详情
create table oauth_client_details (
client_id VARCHAR(256) PRIMARY KEY, -- 客户端应用的账号
resource_ids VARCHAR(256), -- 客户端应用可访问的资源服务器列表,(空代表所有资源服务器都可以访问)
client_secret VARCHAR(256), -- 客户端应用的密码
scope VARCHAR(256), -- 资源服务器拥有的所有权限列表 (get add delete update)
authorized_grant_types VARCHAR(256), -- 客户端支持的授权码模式列表
web_server_redirect_uri VARCHAR(256), -- 授权码模式,申请授权码后重定向的uri.
authorities VARCHAR(256),
access_token_validity INTEGER, -- 设置颁发token的有效期
refresh_token_validity INTEGER, -- 颁发refresh_token的有效期(不设置不会同时颁发refresh_token)
additional_information VARCHAR(4096),
autoapprove VARCHAR(256) -- 设置为true,授权码模式下自动授权
);
create table oauth_client_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(256) PRIMARY KEY,
user_name VARCHAR(256),
client_id VARCHAR(256)
);
-- 存放颁发的token
create table oauth_access_token (
token_id VARCHAR(256),
token BLOB,
authentication_id VARCHAR(256) 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 DATETIME,
lastModifiedAt DATETIME
);
CREATE TABLE `sys_user` (
`id` bigint(32) NOT NULL,
`username` varchar(100) DEFAULT NULL,
`password` varchar(200) DEFAULT NULL,
`enable_` tinyint(1) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`mobile` varchar(20) DEFAULT NULL,
`del_flag` tinyint(1) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`create_user` bigint(32) DEFAULT NULL,
`modified_time` datetime DEFAULT NULL,
`modified_user` bigint(32) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('app', NULL, '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', 'server', 'password,authorization_code,refresh_token,client_credentials', NULL, NULL, 60000, 300, NULL, NULL);
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('order', NULL, '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', 'server', 'password,authorization_code,refresh_token,client_credentials', NULL, NULL, 60000, 300, NULL, NULL);
INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('user', NULL, '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', 'server', 'password,authorization_code,refresh_token,client_credentials', NULL, NULL, 60000, 300, NULL, NULL);
INSERT INTO `sys_user` VALUES (1282941563927805954, 'trazen', '$2a$10$JWSnLszKQANW7OF3p2b8IuIQXTVD8OUN//Q4l/sZGmzyaLEWnC5/u', NULL, 'trazen@126.com', '18559756159', 0, '2020-07-14 15:34:39', NULL, '2020-07-14 15:40:45', NULL);
2.3 Добавить конфигурацию
bootstrap.yml настроен следующим образом
server:
port: 8889
spring:
application:
name: auth-server
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n'
config: classpath:logback-spring.xml
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka/
application.yml настраивается следующим образом:
# mybatis- plus配置
mybatis-plus:
# xml扫描,多个目录用逗号或者分号隔开隔开
mapper-locations: classpath:mapper/*.xml
# 以下配置均有默认值,可以不设置
global-config:
db-config:
#主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: ASSIGN_ID
configuration:
# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
map-underscore-to-camel-case: true
# 返回map时true:当查询数据为空时字段返回为null,false:不加这个查询数据为空时,字段将被隐藏
call-setters-on-nulls: true
spring:
datasource:
#driver-class-name: com.mysql.cj.jdbc.Driver
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:p6spy:mysql://192.168.29.188:3306/sc_oauth2?useUnicode=true&characterEncoding=utf-8
username: root
password: root
# 初始连接数
initial-size: 10
# 最大连接池数量
max-active: 100
# 最小连接池数量
min-idle: 10
# 配置获取连接等待超时的时间
max-wait: 60000
# 打开PSCache,并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: false
2.4 Настройка службы сведений о пользователе
- Запрос клиента на выдачу токена на самом деле заключается в доступе к конечной точке токена (TokenEndPoint), которая была инкапсулирована Spring Security oauth2. Интерфейс в конечном итоге вызовет метод UserDetailsService для получения информации о пользователе в соответствии с запрошенной информацией об учетной записи. Обратите внимание, что это только для получения информации о пользователе Проверка информации об учетной записи выполняется Spring Security, поэтому этот интерфейс переписан для получения информации о пользователе из таблицы Mysql.
- Класс UserDetailsService заключается в том, что когда пользователь запрашивает токен, информация о пользователе может быть загружена в соответствии с именем пользователя и возвращена.После операции проверки пароля учетной записи пользователя AuthenticationManager вызовет аутентификатор для проверки пароля учетной записи пользователя. .
интерфейс MyUserDetailsService.java
/**
* 功能描述: 继承UserDetailsService接口
* @author Trazen
* @date 2020/7/14 15:43
*/
public interface MyUserDetailsService extends UserDetailsService {
}
Реализовать класс MyUserDetailsServiceImpl
/**
* 功能描述: 自定义UserDetailsService
* @author Trazen
* @date 2020/7/14 15:43
*/
@Primary
@Service
@AllArgsConstructor
public class MyUserDetailsServiceImpl implements MyUserDetailsService {
private final SysUserMapper sysUserMapper;
@Override
public AuthUserDetail loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(SysUser::getUsername,username);
SysUser sysUser = sysUserMapper.selectOne(wrapper);
if(sysUser == null){
throw new UsernameNotFoundException("用户不存在");
}else {
return UserDetailConverter.convert(sysUser);
}
}
public static class UserDetailConverter{
static AuthUserDetail convert(SysUser user){
return new AuthUserDetail(user);
}
}
}
2.5 Конфигурация аутентификации и авторизации
Весенняя конфигурация безопасности центра аутентификации выглядит следующим образом: Oauth2WebSecurityConfig.java
/**
* 功能描述: spring security 配置
* @author Trazen
* @date 2020/7/14 16:00
*/
@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class Oauth2WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final MyUserDetailsService myUserDetailsService;
/**
* 重新注入认证管理器
* @return
* @throws Exception
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 注入密码加密BCryptPasswordEncoder
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义方式加载用户信息
auth.userDetailsService(myUserDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.csrf().disable()
.httpBasic();
}
}
2.6 Расширить энхансер токена TokenEnhancer
В предыдущей статье Spring Security Oauth2 вы могли узнать, что интерфейс по умолчанию для получения токена будет получать только accessToken, refreshToken и другую информацию. В практических сценариях приложений нам также может потребоваться добавить некоторую информацию, связанную с пользователем (например, идентификатор пользователя, имя пользователя, идентификатор организации пользователя и т. д.) В настоящее время информация, возвращаемая по умолчанию, не может удовлетворить наши потребности. Возьмите на себя и переопределите его реализацию метода повышения.
MyTokenEnhancer.java
/**
* 功能描述: 增强颁发的token的携带信息
* @author Trazen
* @date 2020/7/14 16:07
*/
public class MyTokenEnhancer implements TokenEnhancer {
/**
* 客户端模式
*/
private final static String CLIENT_CREDENTIALS = "client_credentials";
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
//客户端模式不进行增强
if (CLIENT_CREDENTIALS.equals(authentication.getOAuth2Request().getGrantType())) {
return accessToken;
}
//获取要增强携带的字段
AuthUserDetail authUserDetail = (AuthUserDetail) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>(3);
//添加token携带的字段
additionalInfo.put("id", authUserDetail.getSysUser().getId());
additionalInfo.put("username", authUserDetail.getSysUser().getUsername());
additionalInfo.put("email", authUserDetail.getSysUser().getEmail());
additionalInfo.put("mobile", authUserDetail.getSysUser().getMobile());
DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
token.setAdditionalInformation(additionalInfo);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
2.7 Конфигурация сервера аутентификации и авторизации
Настройте компоненты oauth2 центра аутентификации, такие как настраиваемый TokenEnhancer, настраиваемый UserDetailService, перенастроенный AuthenticationManager и источник данных ранее созданной таблицы базы данных.
Код сервера аутентификации и авторизации выглядит следующим образом Oauth2AuthServerConfig.java
/**
* 功能描述: oauth2 认证服务器配置
* @author Trazen
* @date 2020/7/14 16:11
*/
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class Oauth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final DataSource dataSource;
private final MyUserDetailsService myUserDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
MyClientDetailsService clientDetailsService = new MyClientDetailsService(dataSource);
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 将增强的token设置到增强链中
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer(), jwtAccessTokenConverter()));
endpoints.tokenStore(tokenStore())
.authenticationManager(authenticationManager)
// //刷新token的请求会用用到
.userDetailsService(myUserDetailsService)
.tokenEnhancer(enhancerChain);
}
/**
* 更改存储token的策略,默认是内存策略,修改为jwt
* @return
*/
@Bean
public TokenStore tokenStore() {
//基于token认证
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jat = new JwtAccessTokenConverter();
// jwt使用这个key来签名,验证token的服务也使用这个key来验签
jat.setSigningKey("hxmec");
return jat;
}
/**
* 添加自定义token增强器实现颁发额外信息的token,因为默认颁发token的字段只有username和ROLE
* @return
*/
@Bean
public TokenEnhancer customTokenEnhancer() {
//自定义实现
return new MyTokenEnhancer();
}
}
2.8 Проверка сервера аутентификации и авторизации
Запустите проекты eureka-server и auth-server, чтобы проверить получение токена доступа. В тестовых данных используются данные, созданные в предыдущем скрипте (для шифрования пароля используется метод BCryptPasswordEncoder). Тестовые данные oauth_client_details выглядят следующим образом: client_id:приложение client_secret: 123456
Данные теста sys_user следующие: имя пользователя: тразен пароль: 123456
Конкретные этапы тестирования следующие: Адрес конечной точки для получения токена: http://{ip}:{port}/oauth/token
Возвращенный access_tokenken можно найти на официальном сайте jwt [Audition.IO/#decoded - Сегодня вечером…] для разрешения адреса.
3. Создайте проект auth-client
Этот проект в основном инкапсулирует конфигурацию, связанную с сервером ресурсов, как общую зависимость.
3.1 Добавить зависимости maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-oauth2</artifactId>
<groupId>com.hxmec</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>auth-client</artifactId>
<dependencies>
<!-- security ouath2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.3.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 Переписать анализатор токенов
код показывает, как показано ниже: MyUserAuthenticationConverter.java
/**
* 功能描述: 重写token解析器,根据checkToken的结果转化用户信息
* @author Trazen
* @date 2020/7/14 17:16
*/
public class MyUserAuthenticationConverter implements UserAuthenticationConverter {
private static final String N_A = "N/A";
@Override
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) {
return null;
}
@Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (!map.containsKey(USERNAME)){
return null;
}else{
CurrentUser user = CurrentUser.builder()
.id((Long) map.get("id"))
.username((String) map.get(USERNAME))
.email((String) map.get("email"))
.mobile((String) map.get("mobile"))
.build();
// 有权限信息就格式化权限信息
if (map.containsKey("authorities") && map.get("authorities") != null){
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
user.setAuthorities(authorities);
return new UsernamePasswordAuthenticationToken(user, N_A,authorities);
}else {
return new UsernamePasswordAuthenticationToken(user, N_A,null);
}
}
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String) {
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.collectionToCommaDelimitedString((Collection<?>) authorities));
}else if (authorities == null){
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}
3.3 Решить проблему вызова токена, проходящего через интерфейс Feign
Основной код выглядит следующим образом (остальные коды относятся к исходному коду проекта): MyFeignClientInterceptor.java
/**
* 功能描述: 扩展OAuth2FeignRequestInterceptor
* 如果特殊场景比如调度任务调用Feign接口等
* 可以通过过滤指定header头的方式,防止accessTokenContextRelay.copyToken()报错
* @author Trazen
* @date 2020/7/17 21:17
*/
public class MyFeignClientInterceptor extends OAuth2FeignRequestInterceptor {
private final OAuth2ClientContext oAuth2ClientContext;
private final AccessTokenContextRelay accessTokenContextRelay;
/**
* Default constructor which uses the provided OAuth2ClientContext and Bearer tokens
* within Authorization header
*
* @param oAuth2ClientContext provided context
* @param resource type of resource to be accessed
* @param accessTokenContextRelay
*/
public MyFeignClientInterceptor(OAuth2ClientContext oAuth2ClientContext
, OAuth2ProtectedResourceDetails resource, AccessTokenContextRelay accessTokenContextRelay) {
super(oAuth2ClientContext, resource);
this.oAuth2ClientContext = oAuth2ClientContext;
this.accessTokenContextRelay = accessTokenContextRelay;
}
/**
* Create a template with the header of provided name and extracted extract
* 1. 如果使用 非web 请求,header 区别
* 2. 根据authentication 还原请求token
*
* @param template
*/
@Override
public void apply(RequestTemplate template) {
accessTokenContextRelay.copyToken();
if (oAuth2ClientContext != null
&& oAuth2ClientContext.getAccessToken() != null) {
super.apply(template);
}
}
}
3.4 Конфигурация сервера ресурсов
код показывает, как показано ниже: MyResourceServerConfig.java
/**
* 功能描述: 资源服务器配置
* @author Trazen
* @date 2020/7/14 21:37
*/
@Slf4j
@Configuration
@EnableResourceServer
@AllArgsConstructor
@ComponentScan("com.hxmec.auth")
@EnableConfigurationProperties(AuthClientProperties.class)
public class MyResourceServerConfig extends ResourceServerConfigurerAdapter {
private final MyAuthenticationEntryPoint baseAuthenticationEntryPoint;
private final AuthClientProperties authClientProperties;
private final RestTemplate lbRestTemplate;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// 放行 swagger ui (有整合swagger就放行这些请求吧)
http.authorizeRequests().antMatchers(
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/api/**/v2/api-docs")
.permitAll();
// 根据自定义配置url放行
if (authClientProperties.getIgnoreUrls() != null){
for(String url: authClientProperties.getIgnoreUrls()){
http.authorizeRequests().antMatchers(url).permitAll();
}
}
// 其他请求均需要token才能访问
http.authorizeRequests().anyRequest().authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
if (authClientProperties.getResourceId() != null) {
resources.resourceId(authClientProperties.getResourceId());
}
// 这里的签名key 保持和认证中心一致
if (authClientProperties.getSigningKey() == null) {
log.info("SigningKey is null cant not decode token.......");
}
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
accessTokenConverter.setUserTokenConverter(new MyUserAuthenticationConverter());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//设置解析jwt的密钥
converter.setSigningKey(authClientProperties.getSigningKey());
converter.setVerifier(new MacSigner(authClientProperties.getSigningKey()));
MyTokenServices tokenServices = new MyTokenServices();
// 在CustomTokenServices注入三个依赖对象
//设置token存储策略
tokenServices.setTokenStore(new JwtTokenStore(converter));
tokenServices.setJwtAccessTokenConverter(converter);
tokenServices.setDefaultAccessTokenConverter(accessTokenConverter);
tokenServices.setRestTemplate(lbRestTemplate);
resources.tokenServices(tokenServices)
.authenticationEntryPoint(baseAuthenticationEntryPoint);
}
}
4. Создайте проект пользователя-сервера/сервера-заказа приложения сервера ресурсов.
Этапы построения приложения сервера ресурсов такие же, сервер заказов в основном демонстрирует функцию передачи токена через вызов интерфейса feign.
4.1 Добавление зависимостей maven
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-oauth2</artifactId>
<groupId>com.hxmec</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-server</artifactId>
<dependencies>
<dependency>
<groupId>com.hxmec</groupId>
<artifactId>auth-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- SpringRetry 重试框架依赖 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 Создайте класс запуска Spring Boot
UserServerApplication.java
/**
* 功能描述: User Server启动类
* @author Trazen
* @date 2020/7/15 9:55
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class UserServerApplication {
public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class, args);
}
}
4.3 Добавить конфигурацию
конфигурация bootstrap.yml
server:
port: 8890
spring:
application:
name: user-server
cloud:
loadbalancer:
retry:
#开启重试机制
enabled: true
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} - %msg%n'
config: classpath:logback-spring.xml
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka/
конфигурация application.yml
hx:
oauth2:
client:
# jwt的密钥
signingKey: hxmec
resourceId: ${spring.application.name}
# 放行的url
ignoreUrls:
- /oauth/**
- /user/**
#ribbon全局配置
ribbon:
#处理请求的超时时间,单位ms,默认1000
ReadTimeout: 3000
#连接建立的超时时间,单位ms,默认1000
ConnectTimeout: 3000
feign:
compression:
request:
#是否启用请求GZIP压缩,true:启用,false:不启用
enabled: true
#压缩支持的MIME TYPE
mime-types: text/xml,application/xml,application/json
#压缩数据的最小值
min-request-size: 2048
response:
#是否启用响应GZIP压缩,true:启用,false:不启用
enabled: true
client:
config:
#feign全局配置
default:
#指定日志级别,none:不记录任何日志,basic:仅记录请求方法、URL、响应状态代码以及执行时间(适合生产环境)
#headers:在basic基础上,记录请求和响应的header,full:记录请求和响应的header、body和元数据,默认none
loggerLevel: basic
#feign指定客户端配置,即仅对指定调用的服务生效
eureka-client:
loggerLevel: full
4.4 Написание тестовых интерфейсов
UserController.java
/**
* 功能描述:
* @author Trazen
* @date 2020/7/15 10:39
*/
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@GetMapping("/a")
@PreAuthorize("isAuthenticated()")
public String get(@AuthenticationPrincipal CurrentUser currentUser){
return "1";
}
@GetMapping("/b")
public String get02(){
log.info("---------------->{}",SecurityUtils.getCurrentUser());
return SecurityUtils.getCurrentUser().getUsername();
}
@GetMapping("/c")
public String get03(@AuthenticationPrincipal CurrentUser currentUser){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
log.info("---------------->{}",currentUser);
return "3";
}
}
4.5 Разверните сервер заказов, как указано выше
Демонстрационный код вызова интерфейса Feign выглядит следующим образом:
/**
* 功能描述:
* @author Trazen
* @date 2020/7/15 17:41
*/
@FeignClient(value = "user-server")
public interface UserFeignApi {
/**
* user服务b接口
* @return
*/
@GetMapping("/user/b")
String get02();
}
/**
* 功能描述:
* @author Trazen
* @date 2020/7/15 16:16
*/
@RestController
@RequestMapping("/order")
@Slf4j
@AllArgsConstructor
public class OrderController {
private final UserFeignApi userFeignApi;
@GetMapping("/a")
public String get02(@AuthenticationPrincipal CurrentUser currentUser){
log.info("---------------->{}", SecurityUtils.getCurrentUser());
return "1";
}
@GetMapping("/b")
public String getFeign(){
return userFeignApi.get02();
}
}
4.6 Запустите проект для тестирования
Запустите следующие проекты: эврика-сервер----> сервер-аутентификации----> сервер-пользователь/сервер-заказа
- 1. Получить Токен через интерфейс службы аутентификации и авторизации
- 2. Принесите токен для доступа к интерфейсу в пользовательском сервере
Если токен действителен и нормально возвращает результат; Если токен недействителен, он вернет пользовательскую информацию об истечении срока авторизации.
- 3. Посетите интерфейс сервера заказов и продемонстрируйте вызов интерфейса feign для передачи токена.