#
如果Function中抛出异常,系统没有配置捕获异常,则异常消息会被丢弃。通常会进行配置。
@ServiceActivator(inputChannel = "my-destination.my-group.errors")
public void handleError(ErrorMessage message) {
Throwable throwable = message.getPayload();
log.error("截获异常", throwable);
Message<?> originalMessage = message.getOriginalMessage();
assert originalMessage != null;
log.info("原始消息体 = {}", new String((byte[]) originalMessage.getPayload()));
}
详情参考:
https://www.itmuch.com/spring-cloud/spring-cloud-stream-error-handling/
SPRING CLOUD STREAM内置了一个RoutingFunction,能将MESSAGE路由到应用的其他FUNCTION中。
对接RoutingFunction可发送消息到其外部DESTINATION中或用“|”连接符连接。
application.yaml
# This setting can increase or decrease the rate of message production (1000 = 1s)
# spring.cloud.stream.poller.fixed-delay=1000
# DefaultPollerProperties
# This setting can control which function method in our code will be triggered if there are multiple
# spring.cloud.function.definition=supplyLoan
# Give the autogenerated binding a friendlier name
spring:
application:
name: loan-check-rabbit
banner:
location: classpath:/banner-rabbit.txt
cloud:
#BindingServiceProperties
stream:
#StreamFunctionProperties
function:
definition: loadCheckerFunction;loanCheckerDecieder;loanCheckerConsumer;\
loanDeclinedConsumer;loanApprovedConsumer;loanCheckerProcessor|functionRouter
routing:
enabled: true
#BindingProperties
bindings:
loanCheckerProcessor|functionRouter-in-0:
destination: queue.pretty.log.messages
binder: local_rabbit
loanApprovedConsumer-in-0:
destination: load.approved
binder: local_rabbit
loanDeclinedConsumer-in-0:
destination: load.declined
binder: local_rabbit
loanCheckerDecieder-in-0:
destination: queue.pretty.log.messages.222
binder: local_rabbit
loanCheckerDecieder-out-0:
destination: queue.pretty.approved.messages
binder: local_rabbit
loanCheckerConsumer-in-0:
destination: queue.pretty.approved.messages
binder: local_rabbit
#BinderProperties
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: 10.80.27.69
port: 5672
username: guest
password: guest
virtual-host: my-virtual-host
logging:
level:
root: info
org.springframework:
cloud.function: debug
#retry: debug
LoanCheckConfiguration.java
package com.paul.testspringcloudstream.loancheck.config;
import java.util.function.Consumer;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.function.context.MessageRoutingCallback;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import com.paul.testspringcloudstream.common.model.Loan;
import com.paul.testspringcloudstream.common.model.Status;
import com.paul.testspringcloudstream.loancheck.router.LoanCheckerRouter;
import com.paul.testspringcloudstream.loancheck.service.LoanProcessor;
import com.paul.testspringcloudstream.loancheck.service.LoanService;
@Configuration
public class LoanCheckConfiguration {
private static final Logger log = LoggerFactory.getLogger(LoanCheckConfiguration.class);
private static final Long MAX_AMOUNT = 10000L;
private static final String LOG_PATTERN = "{} - {} {} for ${} for {}";
@Autowired
public void test(Consumer<Loan> loanCheckerConsumer) {
log.info("{}", loanCheckerConsumer.getClass());
}
@Bean
public Consumer<Loan> loanCheckerConsumer(){
return loan ->
log.info(LOG_PATTERN, "loanCheckerConsumer", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
}
@Bean
public Consumer<Loan> loanDeclinedConsumer(){
return loan ->
log.info(LOG_PATTERN, "loanDeclinedConsumer", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
}
@Bean
public Consumer<Loan> loanApprovedConsumer(){
return loan ->
log.info(LOG_PATTERN, "loanApprovedConsumer", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
}
@Bean
public MessageRoutingCallback loanCheckerRouter() {
return new LoanCheckerRouter();
}
@Bean
public Function<Loan, Loan> loanCheckerProcessor(
LoanService loanService
){
return loan -> loanService.check(loan);
}
@Bean
public Function<Loan, Message<Loan>> loanCheckerProcessorBak(
LoanService loanService
){
return loan -> {
Loan result = loanService.check(loan);
String sendTo = Status.DECLINED.name().equals(result.getStatus()) ?
LoanProcessor.DECLINED_OUT : LoanProcessor.APPROVED_OUT;
return MessageBuilder.withPayload(result)
.setHeader("spring.cloud.stream.sendto.destination", sendTo)
.build();
};
}
@Bean
public Consumer<Loan> loanCheckerDecieder(StreamBridge streamBridge){
return loan -> {
log.info(LOG_PATTERN, "loanCheckerDecieder", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
if (loan.getAmount() > MAX_AMOUNT) {
loan.setStatus(Status.DECLINED.name());
streamBridge.send(LoanProcessor.DECLINED_OUT, "local_rabbit", loan);
} else {
loan.setStatus(Status.APPROVED.name());
streamBridge.send(LoanProcessor.APPROVED_OUT, "local_rabbit", loan);
}
log.info(LOG_PATTERN, "loanCheckerDecieder", loan.getStatus(), loan.getUuid(), loan.getAmount(), loan.getName());
};
}
}
LoanCheckerRouter.java,将路由条件统一在此处
package com.paul.testspringcloudstream.loancheck.router;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.function.context.MessageRoutingCallback;
import org.springframework.messaging.Message;
import com.paul.testspringcloudstream.common.model.Loan;
import com.paul.testspringcloudstream.common.model.Status;
public class LoanCheckerRouter implements MessageRoutingCallback{
private static final Logger log = LoggerFactory.getLogger(LoanCheckerRouter.class);
@Override
public String functionDefinition(Message<?> message) {
// byte[] resultByte = (byte[])message.getPayload();
// String resultString = new String(resultByte);
//
// return "loanDeclinedConsumer";
Loan result = (Loan)message.getPayload();
log.info("Loan status: {}", result.getStatus());
return Status.DECLINED.name().equals(result.getStatus()) ?
"loanDeclinedConsumer" : "loanApprovedConsumer";
}
}
SPRING CLOUD STREAM 3.x 版本时,之前的一些编程模式,如@Enablebindding,@StreamListenner等注释被废弃了,这是由于一些框架的代码必需由用户编写,如配置框架用的Input MessageChannel,Output MessageChannel,连接MessageHandler与MessageChannel等,被视为不必要的动作。为了简化用户代码,于是推出Functional Programming Model。
引入了新名词:Supplier、Function与Consumer。实际上这几个类可视为Adapter,如果之前已经有存在的Service类,且方法名为各种各样,可以重新包装成Supplier、Function与Consumer,并在固定的方法名:apply/get/accept中调用Service的方法。
Supplier
当在配置文件中注入此类型的Bean,并在spring.cloud.stream.function.definition加入此Bean的名称,SPRING CLOUD STREAM就会帮你生成一个Output MessageChannel,并连接上此Bean,后续只需要在BINDDING中加入对应的Destination Name,即可向BROKER发消息了。
Consumer
当在配置文件中注入此类型的Bean,并在spring.cloud.stream.function.definition加入此Bean的名称,SPRING CLOUD STREAM就会帮你生成一个Input MessageChannel,并连接上此Bean,后续只需要在BINDDING中加入对应的Destination Name,即可收到BROKER推送关于此Destination的消息了。
Function
当在配置文件中注入此类型的Bean,并在spring.cloud.stream.function.definition加入此Bean的名称,SPRING CLOUD STREAM就会帮你生成一个Input和Output MessageChannel,并连接上此Bean,后续只需要在BINDDING中分别对Input和Output MessageChannel加入对应的Destination Name1/Name2,即可收到BROKER推送关于此Destination的消息,也可以向BROKER发消息了。
与SPRING INTEGRATION的整合
如果要对消息进行复杂处理,如拆分消息、聚合消息、IF ELSE消息等,就要借助SPRING INTEGRATION了。
@Bean
public IntegrationFlow upperCaseFlow(LoanService loanService) {
return IntegrationFlows
//turn this IntegrationFlow as a gateway, here is a Function interface
//with loadCheckerFunction as bean name
.from(LoadCheckerFunction.class, gateway -> gateway.beanName("loadCheckerFunction"))
.handle(loanService, "check")
.logAndReply(LoggingHandler.Level.WARN);
}
public interface LoadCheckerFunction extends Function<Loan, Loan>{
}
IntegrationFlows.from(Class<?> serviceInterface)是可以将本IntegrationFlow包装成serviceInterface的实现类,如果调用此接口,最终会返回IntegrationFlow最后一个步骤的实体,如果这个serviceInterface是Function的话,刚好和SPRING CLOUD STREAM对接上。
后续在spring.cloud.stream.function.definition加入此Bean的名称loadCheckerFunction,SPRING CLOUD STREAM就会帮你生成一个Input和Output MessageChannel,并连接上此Bean,再在BINDDING中分别对Input和Output MessageChannel加入对应的Destination Name1/Name2,即可收到BROKER推送关于此Destination的消息,也可以向BROKER发消息。
application.yaml
# This setting can increase or decrease the rate of message production (1000 = 1s)
# spring.cloud.stream.poller.fixed-delay=1000
# This setting can control which function method in our code will be triggered if there are multiple
# spring.cloud.function.definition=supplyLoan
# Give the autogenerated binding a friendlier name
spring:
application:
name: loan-check-rabbit
banner:
location: classpath:/banner-rabbit.txt
cloud:
stream:
function.definition: loadCheckerFunction
#BindingProperties
bindings:
loadCheckerFunction-in-0:
destination: queue.pretty.log.messages
binder: local_rabbit
loadCheckerFunction-out-0:
destination: queue.pretty.approved.messages
binder: local_rabbit
#BinderProperties
binders:
local_rabbit:
type: rabbit
environment:
spring:
rabbitmq:
host: 10.80.27.69
port: 5672
username: guest
password: guest
virtual-host: my-virtual-host
Reference
https://spring.io/blog/2019/10/25/spring-cloud-stream-and-spring-integration
安装ERLANG
从这里下载0依赖的ERLANG安装包:
https://github.com/rabbitmq/erlang-rpm/releases 象这种erlang-23.3.4.8-1.el7.x86_64.rpm含el7的是CENTOS7版本,含el8的是CENTOS8版本,安装脚本
yum install -y erlang-23.3.4.8-1.el7.x86_64.rpm
安装RABBITMQ
下载地址:
https://github.com/rabbitmq/rabbitmq-server/releases安装脚本:
yum install -y erlang-23.3.4.8-1.el7.x86_64.rpm拷贝配置文件
下载配置文件样例:
https://github.com/rabbitmq/rabbitmq-server/blob/master/deps/rabbit/docs/rabbitmq.conf.example粘贴并重命名文件:
/etc/rabbitmq/rabbitmq.conf开启WEB控制台
/lib/rabbitmq/bin/rabbitmq-plugins enable rabbitmq_management
配置guest可远程访问
## Uncomment the following line if you want to allow access to the
## guest user from anywhere on the network.
loopback_users.guest = false
配置开机启动
chkconfig rabbitmq-server on
启动实例
systemctl start rabbitmq-serve
systemctl stop rabbitmq-serve
访问控制台,guest/guest
http://10.80.27.69:15672/#/
Reference
https://www.cnblogs.com/ZhuChangwu/p/14093107.htmlhttps://juejin.cn/post/6933040530519506957
通常微服务应用之间的通信是通过HTTP调用,吞吐性不建都高,高并发的场景建议使用EVENT DRIVEN的框架,即使用MESSAGE通信。
即A微服务应用将数据发送到MESSAGE BROKER中的某个DESTINATION,此DESTINATION是广播型,非点对点型。B微服务应用订阅此DESTINATION,当有新MESSAGE到达此DESTINATION时,MESSAGE BROKER会将此MESSAGE推送给B应用。所有对此MESSAGE有需要的应用均可订阅,从而收到此MESSAGE。
SPRING CLOUD 中EVENT DRIVEN的框架就是SPRING CLOUD STREAM。其底层是使用SPRING INTEGRATION实现。
SPRING CLOUD STREAM有以下新名词:
是对MESSAGE BROKER操作方法的抽象,即应用通过此BINDER操作MESSAGE BROKER。目前只实现了RABITMQ和KAFKA。
MESSAGE从SPRING CLOUD STREAM传给应用或相反是通过CHANNEL传递的,这点和SPRING INTEGRATION是一样的。
MESSAGE从应用传给SPRING CLOUD STREAM的CHANNEL,叫@INPUT,包含这种CHANNEL的接口叫SOURCE。
MESSAGE从SPRING CLOUD STREAM传给应用的CHANNEL,叫@OUPUT,包含这种CHANNEL的接口叫SINK。
绑定哪个@INPUT或哪个@OUPUT与哪个DESTINATION发送或接收关系的MAPPING。
应用启动时就会建立EnableBinding指定的接口中的CHANNEL
默认下如果同一个应用部署了多个实例,则每个实例都会收到MESSAGE,这时如果设置了消费者群组名称,则同一个名称下的多个实例,只有一个能收到MESSAGE。
如果为MESSAGE指定规则,如MESSAGE某个字段值以A开头为一个规则,以B开头为一个规则,那么以A开头的MESSAGE会放到同一个分区中。
这样使用就很简单了,只要取得OUTPUT CHANNEL,就可以发送MESSAGE,将代码关联到INPUT CHANNEL,就能在收到MESSAGE时,相关代码就会被执行。
摘要: 根据OAUTH2协议,如果需要用户协助的,则使用authorization_code流程,此时需要用户登录页面、CLIENT SERVER、RESOURCE SERVER和AUTHORIZATION SERVER,其中CLIENT SERVER是通过http调用RESOURCE SERVER的api,AUTHORIZATION SERVER使用现成的KEYCLOAK。如果不需要用户协助的,即SER...
阅读全文
employee-service调用department-service,如果要按OAUTH2.0流程,只需要提供client-id和client-secrect即可。在KEYCLOAK中引入service-account,即配置该employee-service时,取消standard-flow,同时激活service-account。
employee-service的application.yaml文件,其中的public-key要从KEYCLOAK中取
server:
port: 8090
# Can be set to false to disable security during local development
rest:
security:
enabled: true
#issuer-uri: http://localhost:8080/auth/realms/dev
api-matcher: /api/**
cors:
allowed-origins: '*'
allowed-headers: '*'
allowed-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
max-age: 3600
security:
oauth2:
resource:
filter-order: 3
id: test-employee-service
token-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
user-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/userinfo
jwt:
key-value: |
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
-----END PUBLIC KEY-----
# To access another secured micro-service
client:
client-id: test-employee-service
#client-secret: 25c33006-e1b9-4fc2-a6b9-c43dbc41ecd0
user-authorization-uri: ${rest.security.issuer-uri}/protocol/openid-connect/auth
access-token-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token
scope: openid
grant-type: client_credentials
is-client-only: true
#Logging Configuration
logging:
level:
org.springframework.boot.autoconfigure.logging: INFO
org.springframework.security: DEBUG
org.arun: DEBUG
root: INFO
application-dev.yaml
rest:
security:
issuer-uri: http://10.80.27.69:8180/auth/realms/quickstart
department-service:
url: http://10.80.27.69:8095/api/departments/1
security:
oauth2:
client:
client-secret: db25cdbd-605b-429d-bd92-96705bdf1474
department-service的application.yaml
server:
port: 8095
# Can be set to false to disable security during local development
rest:
security:
enabled: true
#issuer-uri: http://localhost:8080/auth/realms/dev
api-matcher: /api/**
cors:
allowed-origins: '*'
allowed-headers: '*'
allowed-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS
max-age: 3600
security:
oauth2:
resource:
filter-order: 3
id: test-department-service
token-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/token/introspect
user-info-uri: ${rest.security.issuer-uri}/protocol/openid-connect/userinfo
jwt:
key-value: |
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB
-----END PUBLIC KEY-----
#Logging Configuration
logging:
level:
org.springframework.boot.autoconfigure.logging: INFO
org.springframework.security: DEBUG
org.arun: DEBUG
root: INFO
application-dev.yaml
rest:
security:
issuer-uri: http://10.80.27.69:8180/auth/realms/quickstart
employee-service的pom.xml
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.18.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>org.arun.springoauth</groupId>
<artifactId>spring-oauth2-employee-service</artifactId>
<version>1.0.0</version>
<name>spring-oauth2-employee-service</name>
<description>Employee Service</description>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.1.18.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<!-- <version>2.1.18.RELEASE</version> -->
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>ZIP</layout>
<excludes>
<exclude>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclude>
</excludes>
<includes>
<include>
<groupId>com.paul</groupId>
</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>
将jwt格式的access_token转成Authentication的类JwtAccessTokenCustomizer
package org.arun.springoauth.employee.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtAccessTokenConverterConfigurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
@Configuration
public class JwtAccessTokenCustomizer extends DefaultAccessTokenConverter implements JwtAccessTokenConverterConfigurer {
private static final Logger LOG = LoggerFactory.getLogger(JwtAccessTokenCustomizer.class);
private static final String CLIENT_NAME_ELEMENT_IN_JWT = "resource_access";
private static final String ROLE_ELEMENT_IN_JWT = "roles";
private ObjectMapper mapper;
@Autowired
public JwtAccessTokenCustomizer(ObjectMapper mapper) {
this.mapper = mapper;
LOG.info("Initialized {}", JwtAccessTokenCustomizer.class.getSimpleName());
}
@Override
public void configure(JwtAccessTokenConverter converter) {
converter.setAccessTokenConverter(this);
LOG.info("Configured {}", JwtAccessTokenConverter.class.getSimpleName());
}
/**
* Spring oauth2 expects roles under authorities element in tokenMap, but
* keycloak provides it under resource_access. Hence extractAuthentication
* method is overriden to extract roles from resource_access.
*
* @return OAuth2Authentication with authorities for given application
*/
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> tokenMap) {
LOG.debug("Begin extractAuthentication: tokenMap = {}", tokenMap);
JsonNode token = mapper.convertValue(tokenMap, JsonNode.class);
Set<String> audienceList = extractClients(token); // extracting client names
List<GrantedAuthority> authorities = extractRoles(token); // extracting client roles
OAuth2Authentication authentication = super.extractAuthentication(tokenMap);
OAuth2Request oAuth2Request = authentication.getOAuth2Request();
OAuth2Request request = new OAuth2Request(oAuth2Request.getRequestParameters(), oAuth2Request.getClientId(),
authorities, true, oAuth2Request.getScope(), audienceList, null, null, null);
Authentication usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(
authentication.getPrincipal(), "N/A", authorities);
LOG.debug("End extractAuthentication");
return new OAuth2Authentication(request, usernamePasswordAuthentication);
}
private List<GrantedAuthority> extractRoles(JsonNode jwt) {
LOG.debug("Begin extractRoles: jwt = {}", jwt);
Set<String> rolesWithPrefix = new HashSet<>();
jwt.path(CLIENT_NAME_ELEMENT_IN_JWT).elements().forEachRemaining(e -> e.path(ROLE_ELEMENT_IN_JWT).elements()
.forEachRemaining(r -> rolesWithPrefix.add("ROLE_" + r.asText())));
final List<GrantedAuthority> authorityList = AuthorityUtils
.createAuthorityList(rolesWithPrefix.toArray(new String[0]));
LOG.debug("End extractRoles: roles = {}", authorityList);
return authorityList;
}
private Set<String> extractClients(JsonNode jwt) {
LOG.debug("Begin extractClients: jwt = {}", jwt);
if (jwt.has(CLIENT_NAME_ELEMENT_IN_JWT)) {
JsonNode resourceAccessJsonNode = jwt.path(CLIENT_NAME_ELEMENT_IN_JWT);
final Set<String> clientNames = new HashSet<>();
resourceAccessJsonNode.fieldNames().forEachRemaining(clientNames::add);
LOG.debug("End extractClients: clients = {}", clientNames);
return clientNames;
} else {
throw new IllegalArgumentException(
"Expected element " + CLIENT_NAME_ELEMENT_IN_JWT + " not found in token");
}
}
}
Reference
https://medium.com/@bcarunmail/securing-rest-api-using-keycloak-and-spring-oauth2-6ddf3a1efcc2