shiro buji pac4j cas single sign out not work - spring-boot

spring boot 2.2.5
shiro-spring-boot-web-starter 1.5.1
buji-pac4j 4.1.1
pac4j-cas 3.8.3
cas overlay template 5.3.
I start cas server in tomcat with https, and start two clients(pac4j1 and pac4j2) in eclipse.
single sign on works, but single sign out failed.
Following are my configs:
I only added one service file under cas server which looks like:
{
"#class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "^(http)://localhost.*",
"name": "local",
"id": 10000003,
"evaluationOrder": 1
}
application.yml of pac4j1:
server:
port: 8444
servlet:
context-path: /pac4j1
cas:
client-name: pac4j1Client
server:
url: https://localhost:8443/cas
project:
url: http://localhost:8444/pac4j1
Pac4jConfig:
#Configuration
public class Pac4jConfig {
#Value("${cas.server.url}")
private String casServerUrl;
#Value("${cas.project.url}")
private String projectUrl;
#Value("${cas.client-name}")
private String clientName;
#Bean("authcConfig")
public Config config(CasClient casClient, ShiroSessionStore shiroSessionStore) {
Config config = new Config(casClient);
config.setSessionStore(shiroSessionStore);
return config;
}
#Bean
public ShiroSessionStore shiroSessionStore(){
return new ShiroSessionStore();
}
#Bean
public CasClient casClient(CasConfiguration casConfig){
CasClient casClient = new CasClient(casConfig);
casClient.setCallbackUrl(projectUrl + "/callback?client_name=" + clientName);
casClient.setName(clientName);
return casClient;
}
#Bean
public CasConfiguration casConfig(){
final CasConfiguration configuration = new CasConfiguration();
configuration.setLoginUrl(casServerUrl + "/login");
configuration.setProtocol(CasProtocol.CAS20);
configuration.setAcceptAnyProxy(true);
configuration.setPrefixUrl(casServerUrl + "/");
return configuration;
}
}
shiro config:
#Configuration
public class ShiroConfig {
#Value("${cas.project.url}")
private String projectUrl;
#Value("${cas.server.url}")
private String casServerUrl;
#Value("${cas.client-name}")
private String clientName;
#Bean("securityManager")
public DefaultWebSecurityManager securityManager(Pac4jSubjectFactory subjectFactory, CasRealm casRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(casRealm);
manager.setSubjectFactory(subjectFactory);
return manager;
}
#Bean
public CasRealm casRealm(){
CasRealm realm = new CasRealm();
realm.setClientName(clientName);
realm.setCachingEnabled(false);
realm.setAuthenticationCachingEnabled(false);
realm.setAuthorizationCachingEnabled(false);
return realm;
}
#Bean
public Pac4jSubjectFactory subjectFactory(){
return new Pac4jSubjectFactory();
}
#Bean
public FilterRegistrationBean<SingleSignOutFilter> singleSignOutFilter() {
FilterRegistrationBean<SingleSignOutFilter> bean = new FilterRegistrationBean<SingleSignOutFilter>();
bean.setName("singleSignOutFilter");
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(casServerUrl);
singleSignOutFilter.setIgnoreInitConfiguration(true);
bean.setFilter(singleSignOutFilter);
bean.addUrlPatterns("/*");
bean.setEnabled(true);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
#Bean
public FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean() {
FilterRegistrationBean<DelegatingFilterProxy> filterRegistration = new FilterRegistrationBean<DelegatingFilterProxy>();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*");
filterRegistration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD);
return filterRegistration;
}
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/", "securityFilter");
filterChainDefinitionMap.put("/index", "securityFilter");
filterChainDefinitionMap.put("/callback", "callbackFilter");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
#Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, Config config) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
loadShiroFilterChain(shiroFilterFactoryBean);
Map<String, Filter> filters = new HashMap<>(3);
SecurityFilter securityFilter = new SecurityFilter();
securityFilter.setConfig(config);
securityFilter.setClients(clientName);
filters.put("securityFilter", securityFilter);
MyCallbackFilter callbackFilter = new MyCallbackFilter();
callbackFilter.setConfig(config);
callbackFilter.setDefaultUrl(projectUrl);
filters.put("callbackFilter", callbackFilter);
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setConfig(config);
logoutFilter.setCentralLogout(true);
logoutFilter.setLocalLogout(true);
logoutFilter.setDefaultUrl(projectUrl + "/callback?client_name=" + clientName);
filters.put("logout",logoutFilter);
shiroFilterFactoryBean.setFilters(filters);
return shiroFilterFactoryBean;
}
}
application.properties of cas server is default, and cas server use https(https://localhost:8443/cas) while cas clients are http(http://localhost:8444/pac4j1).
Where am I wrong?

with the help of the link SLO which provided by leopal, i know that cas server need to send log out request back to client.
Hence, i checked the log of cas server and found INFO [org.apereo.cas.logout.DefaultLogoutManager] - <Performing logout operations for.
so i added log for org.apereo.cas.logout and found that there are some classes about logout: DefaultLogoutManager, DefaultSingleLogoutServiceLogoutUrlBuilder, DefaultSingleLogoutServiceMessageHandler and SimpleUrlValidator.
when performing logout, DefaultSingleLogoutServiceLogoutUrlBuilder.determineLogoutUrl will get the logout url from registered service or get the original url from cas client if original url is a valid url.
So my problem is : i didn't define logout url in service json file and the original url from cas client is localhost:8444 which is a invalid ipv4. As a result, cas server will not send logout request back to client.
Solution is : use ip in project url instead of localhost in application.yml of cas client:
cas:
client-name: pac4j1Client
server:
url: https://localhost:8443/cas
project:
url: http://192.168.2.119:8444/pac4j1
another solution is set logoutUrl for each cas client service json file(not tried yet).

Related

Spring boot rabbitmq no exchange '"xxxxxxx"' in vhost '/'

I'm writing a simple rabbitmq producer with spring boot 2.2.7.
On the broker side I've setup a direct exchange samples , a queue named samples.default and binded them together adding a samples.default bindkey key.
when running the application I get the following error
Attempting to connect to: [127.0.0.1:5672]
2020-05-14 15:13:39.232 INFO 28393 --- [nio-8080-exec-1] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#2f860823:0/SimpleConnection#3946e760 [delegate=amqp://open-si#127.0.0.1:5672/, localPort= 34710]
2020-05-14 15:13:39.267 ERROR 28393 --- [ 127.0.0.1:5672] o.s.a.r.c.CachingConnectionFactory : Channel shutdown: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange '"samples"' in vhost '/', class-id=60, method-id=40)
The rabbitmq server configuration is correct as I've a python producer that already puts messages succesfully in the "samples.default" queue.
in Spring boot I'm using jackson serialization, but that's not the prolem here I think as I've tested the code without the Jakson serialization configuration and the problem is still the same.
My broker configuration is set both in the application.properties :
#spring.rabbitmq.host=localhost
spring.rabbitmq.addresses=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=xxxx
spring.rabbitmq.password=xxxx
broker.exchange = "samples"
broker.routingKey = "samples.default"
note that using spring.rabbitmq.host doesn't work as it results in using my internet provider address !
and in a BrokerConf configuration class :
#Configuration
public class BrokerConf {
#Bean("publisher")
MessagePublisher<BaseSample> baseSamplePublisher(RabbitTemplate rabbitTemplate) {
return new MessagePublisher<BaseSample>(rabbitTemplate);
}
#Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
final var rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(producerJackson2MessageConverter());
return rabbitTemplate;
}
#Bean
public MessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
The publisher base class is as :
#Component
public class MessagePublisher<T> {
private static final Logger log = LoggerFactory.getLogger(MessagePublisher.class);
private final RabbitTemplate rabbitTemplate;
public MessagePublisher(RabbitTemplate r) {
rabbitTemplate = r;
}
public void publish(List<BaseSample> messages, String exchange, String routingKey) {
for (BaseSample message: messages) {
rabbitTemplate.convertAndSend(exchange, routingKey, message);
}
}
}
that I use in a rest controller
private static final Logger logger = LoggerFactory.getLogger(SamplesController.class);
#Autowired
private MessagePublisher<BaseSample> publisher;
#Value("${broker.exchange}")
private String exchange;
#Value("${broker.routingKey}")
private String routingKey;
#PutMapping(value = "/new", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<SampleAck> add(#RequestBody List<BaseSample> samples) {
publisher.publish(samples, exchange, routingKey);
return ResponseEntity.ok(new SampleAck(samples.size(), new Date()));
}
So the broker connection is OK but the exchange is not found
and rabbitmq resources exists
xxxxxx#xxxxxxx:~/factory/udc-collector$ sudo rabbitmqctl list_exchanges
Listing exchanges for vhost / ...
name type
amq.topic topic
amq.rabbitmq.trace topic
amq.match headers
amq.direct direct
amq.fanout fanout
direct
amq.rabbitmq.log topic
amq.headers headers
samples direct
xxxx#xxxxx:~/factory/udc-collector$ sudo rabbitmqctl list_queues
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
name messages
samples.default 2
Any idea ?
thanks in advance.
The error seems quite obvious:
no exchange '"samples"' in vhost
broker.exchange = "samples"
broker.routingKey = "samples.default"
Remove the quotes
broker.exchange=samples
broker.routingKey=samples.default

spring-boot customize Jetty SSLContextFactory

From reading the spring-boot docs, it seems like the standard way to customize the Jetty server is to implement a class like the following:
#Component
public class JettyServerCustomizer
implements WebServerFactoryCustomizer<JettyServletWebServerFactory> {
#Autowired
private ServerProperties serverProperties;
#Override
public void customize(final JettyServletWebServerFactory factory) {
factory.addServerCustomizers((server) -> {
// Customize
});
}
}
I'm specifically interested in modifying the SSLContextFactory.
Tracing through the spring-boot code, right before the customizers are called, ssl is configured:
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(server, address);
}
for (JettyServerCustomizer customizer : getServerCustomizers()) {
customizer.customize(server);
}
customizeSsl is a private method so cannot be overridden easily:
private void customizeSsl(Server server, InetSocketAddress address) {
new SslServerCustomizer(address, getSsl(), getSslStoreProvider(), getHttp2()).customize(server);
}
One option is to create the context factory and connector ourselves in the customizer, and then overwrite the connectors on the server. This would probably work but it feels like we are re-creating a bunch of code that spring-boot is already doing just to be able to call a method on the SSLContextFactory.
It seems like if we could somehow provider our own SslServerCustomizer then we could do the custom configuration we want.
Does anyone know of a better way to do this?
On my case it works just fine as:
#SpringBootApplication
#ComponentScan(basePackages = { "org.demo.jetty.*" })
public class DemoWebApplication {
public static void main(String[] args) {
SpringApplication.run(DemoWebApplication.class, args);
}
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
factory.setContextPath("/demo-app");
factory.addServerCustomizers(getJettyConnectorCustomizer());
return factory;
}
private JettyServerCustomizer getJettyConnectorCustomizer() {
return server -> {
final HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setSecureScheme("https");
httpConfiguration.setSecurePort(44333);
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStoreType("PKCS12");
sslContextFactory.setKeyStorePath("C:/jetty-demo/demo_cert.p12");
sslContextFactory.setKeyStorePassword("*****");
sslContextFactory.setKeyManagerPassword("****");
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
ServerConnector httpsConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
new HttpConnectionFactory(httpsConfiguration));
httpsConnector.setPort(44333);
server.setConnectors(new Connector[] { httpsConnector });
server.setStopAtShutdown(true);
server.setStopTimeout(5_000);
};
}
}
You can define also a HTTP connector and add it to the customized section
...
ServerConnector connector = new ServerConnector(server);
connector.addConnectionFactory(new HttpConnectionFactory(httpConfiguration));
connector.setPort(8081);
server.setConnectors(new Connector[]{connector, httpsConnector});
...

Spring Integration - Dynamic MailReceiver configuration

I'm pretty new to spring-integration anyway I'm using it in order to receive mails and elaborate them.
I used this spring configuration class:
#Configuration
#EnableIntegration
#PropertySource(value = { "classpath:configuration.properties" }, encoding = "UTF-8", ignoreResourceNotFound = false)
public class MailReceiverConfiguration {
private static final Log logger = LogFactory.getLog(MailReceiverConfiguration.class);
#Autowired
private EmailTransformerService emailTransformerService;
// Configurazione AE
#Bean
public MessageChannel inboundChannelAE() {
return new DirectChannel();
}
#Bean(name= {"aeProps"})
public Properties aeProps() {
Properties javaMailPropertiesAE = new Properties();
javaMailPropertiesAE.put("mail.store.protocol", "imap");
javaMailPropertiesAE.put("mail.debug", Boolean.TRUE);
javaMailPropertiesAE.put("mail.auth.debug", Boolean.TRUE);
javaMailPropertiesAE.put("mail.smtp.socketFactory.fallback", "false");
javaMailPropertiesAE.put("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
return javaMailPropertiesAE;
}
#Bean(name="mailReceiverAE")
public MailReceiver mailReceiverAE(#Autowired MailConfigurationBean mcb, #Autowired #Qualifier("aeProps") Properties javaMailPropertiesAE) throws Exception {
return ConfigurationUtil.getMailReceiver("imap://USERNAME:PASSWORD#MAILSERVER:PORT/INBOX", new BigDecimal(2), javaMailPropertiesAE);
}
#Bean
#InboundChannelAdapter( autoStartup = "true",
channel = "inboundChannelAE",
poller = {#Poller(fixedRate = "${fixed.rate.ae}",
maxMessagesPerPoll = "${max.messages.per.poll.ae}") })
public MailReceivingMessageSource pollForEmailAE(#Autowired MailReceiver mailReceiverAE) {
MailReceivingMessageSource mrms = new MailReceivingMessageSource(mailReceiverAE);
return mrms;
}
#Transformer(inputChannel = "inboundChannelAE", outputChannel = "transformerChannelAE")
public MessageBean transformitAE( MimeMessage mailMessage ) throws Exception {
// amministratore email inbox
MessageBean messageBean = emailTransformerService.transformit(mailMessage);
return messageBean;
}
#Splitter(inputChannel = "transformerChannelAE", outputChannel = "nullChannel")
public List<Message<?>> splitIntoMessagesAE(final MessageBean mb) {
final List<Message<?>> messages = new ArrayList<Message<?>>();
for (EmailFragment emailFragment : mb.getEmailFragments()) {
Message<?> message = MessageBuilder.withPayload(emailFragment.getData())
.setHeader(FileHeaders.FILENAME, emailFragment.getFilename())
.setHeader("directory", emailFragment.getDirectory()).build();
messages.add(message);
}
return messages;
}
}
So far so good.... I start my micro-service and there is this component listening on the specified mail server and mails are downloaded.
Now I have this requirement: mail server configuration (I mean the string "imap://USERNAME:PASSWORD#MAILSERVER:PORT/INBOX") must be taken from a database and it can be configurable. In any time a system administrator can change it and the mail receiver must use the new configuration.
As far as I understood I should create a new instance of MailReceiver when a new configuration is present and use it in the InboundChannelAdapter
Is there any best practice in order to do it? I found this solution: ImapMailReceiver NO STORE attempt on READ-ONLY folder (Failure) [THROTTLED];
In this solution I can inject the ThreadPoolTaskScheduler if I define it in my Configuration class; I can also inject the DirectChannel but every-time I should create a new MailReceiver and a new ImapIdleChannelAdapter without considering this WARN message I get when the
ImapIdleChannelAdapter starts:
java.lang.RuntimeException: No beanfactory at org.springframework.integration.expression.ExpressionUtils.createStandardEvaluationContext(ExpressionUtils.java:79) at org.springframework.integration.mail.AbstractMailReceiver.onInit(AbstractMailReceiver.java:403)
Is there a better way to satisfy my scenario?
Thank you
Angelo
The best way to do this is to use the Java DSL and dynamic flow registration.
Documentation here.
That way, you can unregister the old flow and register a new one, each time the configuration changes.
It will automatically handle injecting dependencies such as the bean factory.

Spring Boot Localhost https issue

I wanted to make my localhost from hypertext transfer protocol to the secured socket layer hypertext transfer protocol. So I have added a key store to my spring boot project, and configured my application.properties like this:
INFORMATION
But I get an error in chrome that:
localhost uses an unsupported protocol.
ERR__VERSION_OR_CIPHER_MISMATCH
Note: I have done no changes to any of the java files.
When I faced this issue I solved it by a bean like below. hope this helps you
#Configuration
class CustomConfiguration {
#Bean
public EmbeddedServletContainerFactory servletContainer() {
final int port = 8443;
final String keystoreFile = "/path/to/keystore"
final String keystorePass = "keystore-password"
final String keystoreType = "pkcs12"
final String keystoreProvider = "SunJSSE"
final String keystoreAlias = "tomcat"
TomcatEmbeddedServletContainerFactory factory =
new TomcatEmbeddedServletContainerFactory(this.port);
factory.addConnectorCustomizers( new TomcatConnectorCustomizer() {
void customize(Connector con) {
Http11NioProtocol proto = (Http11NioProtocol) con.getProtocolHandler();
proto.setSSLEnabled(true);
con.setScheme("https");
con.setSecure(true);
proto.setKeystoreFile(keystoreFile);
proto.setKeystorePass(keystorePass);
proto.setKeystoreType(keystoreType);
proto.setProperty("keystoreProvider", keystoreProvider);
proto.setKeyAlias(keystoreAlias);
}
});
return factory;
}
}
Also make sure you call keytool with -storetype pkcs12, not -storepass pkcs12

HTTP2C (cleartext) client to HTTP2C server

I have an HTTP2C Embedded Jetty 9.x Server running ... note the server connector shows h2c ...
2016-03-21 09:25:44.082:INFO:oejs.ServerConnector:main: Started ServerConnector#66c7bd3f{HTTP/1.1,[http/1.1, h2c, h2c-17, h2c-16, h2c-15, h2c-14]}{0.0.0.0:8080}
I have an OkHttpClient 3 attempting to talk HTTP2C to this server , however it always gets downgraded to HTTP/1.1, what am I missing? Which Java client API supports HTTP2C? My client code is as below ...
package http2;
import java.util.Collections;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class GetClear {
public static void main(String[] args) throws Exception {
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.CLEARTEXT).build();
OkHttpClient client = new OkHttpClient.Builder().connectionSpecs(Collections.singletonList(spec)).build();
Request request = new Request.Builder().url("http://localhost:8080/test").build();
Response response = client.newCall(request).execute();
System.out.println (response.body().string());
System.out.println("****");
response.body().close();
}
}
[The server prints the 'request.getProtocol' from a Jetty servlet and that shows HTTP/1.1 instead of HTTP/2].
HTTP/2 server and client on TLS works just fine using HTTP/2(client code and server code are different of course).
Any help will be truly appreciated.
Using a Jetty HTTP2C client, the same server code works. I guess OkHTTPClient does not support HTTP2C.
A complete h2c example using the HelloHandler example from the jetty doc:
public class HelloServer {
public static class HelloHandler extends AbstractHandler {
final String greeting;
final String body;
public HelloHandler() {
this("Hello World");
}
public HelloHandler(String greeting) {
this(greeting, null);
}
public HelloHandler(String greeting, String body) {
this.greeting = greeting;
this.body = body;
}
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {
response.setContentType("text/html; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
out.println("<h1>" + greeting + "</h1>");
if (body != null) {
out.println(body);
}
baseRequest.setHandled(true);
}
}
public static void main(String[] args) throws Exception {
Server server = new Server();
server.setHandler(new HelloHandler());
HttpConfiguration httpConfig = new HttpConfiguration();
ConnectionFactory h1 = new HttpConnectionFactory(httpConfig);
ConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
ServerConnector serverConnector = new ServerConnector(server, h1, h2c);
serverConnector.setPort(8080);
server.setConnectors(new ServerConnector[] { serverConnector });
server.start();
server.join();
}
}
The Jetty log line shows that you have configured the server connector to have HTTP/1.1 to be the default protocol (that is the upper-case "HTTP/1.1" before the brackets containing the list of protocols supported).
You don't show your server-side code, but you have two choices:
Configure explicitly the default protocol for the server connector:
serverConnector.setDefaultProtocol("h2c");
Pass the ConnectionFactory objects in the right order to the server connector, since the first one will be the default protocol:
HttpConfiguration httpConfig = new HttpConfiguration();
ConnectionFactory h1 = new HttpConnectionFactory(httpConfig);
ConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
ServerConnector serverConnector = new ServerConnector(server, h2c, h1);

Resources