You are not logged in.
Hi,
I'm attempting to integrate ReportServer's authentication with our single-sign-on service.
I'm writing a custom PAM to do so but I haven't been able to figure out one piece - the front-end login page.
I'm considering two approaches. One approach is to redirect all unauthenticated requests to our SSO login page. The other is to create a custom login page to replace the default ReportServer login page.
Can anyone give me an idea on how I can do either of those things?
Offline
Hi sfblake,
which single-sign-on service are you using?
Your can configure it to redirect all unauthenticated requests to your SSO login page. In your reportserver PAM, you can set getClientModuleName() to null as explained here: https://reportserver.net/en/tutorials/t … stom-auth/
"Alternatively, if you want to use a custom login page, you can simply return null."
So your sso-service should be configured to make the redirection, and your PAM to not to use the reportserver login page. The PAM should use your sso-service to check if the user is already authenticated.
You can see a basic example here : https://forum.reportserver.net/viewtopic.php?id=199 #4.
Please let us know if this works.
Regards,
Eduardo
Offline
Thanks for the quick reply, Eduardo.
We have the option to either use Apereo CAS, which requires the redirect, or we can use the Google Identity platform to incorporate Google Sign-In which would need a custom login page.
I had read both the authentication tutorial and the forum post about automatically logging the user in, but I'm still not clear on my initial question. Maybe some more details help. I'll focus on the CAS option to avoid confusing things.
The very high level steps of logging in to ReportServer using CAS for authentication would be:
Initial attempt to access ReportServer
Since the user doesn't have a session, ReportServer redirects the client to the CAS login page
The user signs in through CAS
CAS redirects the user back to ReportServer with a token
ReportServer validates the token with CAS
If the token is valid ReportServer logs the user in
(If needed, more details can be found on their Web flow diagram here: https://apereo.github.io/cas/5.1.x/prot … ocol.html)
So my ReportServer integration would be responsible for steps #2, #5, & #6. I can do #5 and #6, but I don't know how to do #2.
At the point they reach step #2, they aren't authenticated yet, so my PAM's authenticate(AuthToken[] tokens) method would return new AuthenticationResult(false, null). If I also return null in getClientModuleName() I just end up with a blank ReportServer page.
So how to do I, instead, redirect the client to the CAS login page?
Hopefully that will clarify my question. Thanks again for any help.
- Shaun
Offline
Hi Shaun,
Not reportserver but the Apereo AuthenticationFilter should be in charge of the redirection.
Please take a look here:
https://github.com/apereo/java-cas-clie … ilter.java
https://wiki.jasig.org/display/casc/con … he+web.xml
So if you configure reportserver to pass through this filter using tomcat filter mapping, redirection should be done by the Apereo SSO AuthenticationFilter if it detects that the user is not authenticated:
"org.jasig.cas.client.authentication.AuthenticationFilter
The AuthenticationFilter is what detects whether a user needs to be authenticated or not. If a user needs to be authenticated, it will redirect the user to the CAS server."
Please let us know if this works.
Best regards,
Eduardo
Offline
Hi sfblake,
this is a basic example of the CAS PAM. You can see that no redirection is happening here:
import javax.inject.Provider;
import javax.naming.AuthenticationException
import javax.naming.Context
import javax.naming.InvalidNameException
import javax.naming.NamingException
import javax.naming.directory.InitialDirContext
import javax.persistence.NoResultException
import javax.servlet.http.HttpServletRequest;
import net.datenwerke.rs.authenticator.client.login.dto.UserPasswordAuthToken
import net.datenwerke.rs.authenticator.client.login.pam.UserPasswordClientPAM
import net.datenwerke.rs.utils.crypto.PasswordHasher;
import net.datenwerke.security.client.login.AuthToken
import net.datenwerke.security.service.authenticator.AuthenticationResult
import net.datenwerke.security.service.authenticator.ReportServerPAM
import net.datenwerke.security.service.authenticator.hooks.PAMHook
import net.datenwerke.security.service.usermanager.UserManagerService
import net.datenwerke.security.service.usermanager.entities.User
import com.google.inject.Inject
final CasPAM casPam = GLOBALS.injector.getInstance(CasPAM.class);
GLOBALS.services.callbackRegistry.attachHook("CAS_PAM", PAMHook.class, new PAMHook(){
public void beforeStaticPamConfig(LinkedHashSet<ReportServerPAM> pams){
pams.add(casPam);
}
public void afterStaticPamConfig(LinkedHashSet<ReportServerPAM> pams){
}
});
public class CasPAM implements ReportServerPAM {
private UserManagerService userManagerService;
private Provider<HttpServletRequest> httpRequest;
@Inject
public LdapPAM(
UserManagerService userManagerService,
Provider<HttpServletRequest> httpRequest) {
this.userManagerService = userManagerService;
this.httpRequest = httpRequest;
}
public AuthenticationResult authenticate(AuthToken[] tokens) {
String username = httpRequest.get().getRemoteUser();
//System.out.println("###### " + httpRequest.get().getUserPrincipal());
//System.out.println("###### " + httpRequest.get().getRemoteUser());
User u = userManagerService.getUserByName(username);
if(null != u){
return new AuthenticationResult(true, u);
}
return new AuthenticationResult(true, null);
}
public String getClientModuleName() {
return "";
}
}
Best regards,
Eduardo
Offline
Okay, thank you for explaining. I am familiar with configuring CAS in the web.xml. I was initially thinking I should be doing the whole process through ReportServer.
I'll report back if I still have trouble. Thanks again for the help.
- Shaun
Offline
Hi Shaun,
please report back also if you don't have trouble. We are working on a blog entry on configuring SSO with reportserver and the information if this was useful for you would help us.
Best regards,
Eduardo
Offline
Hi Eduardo,
I've attempted to configure CAS through the report server web.xml but I haven't had success, yet, and was hoping you could help again.
Using your CAS PAM script above and configuring CAS through the web.xml, I can get the CAS client to successfully intercept the request to log the user in (to the CAS server) and then subsequently validate the CAS ticket.
When the PAM script runs, though, both httpRequest.get().getUserPrincipal() and httpRequest.get().getRemoteUser() are null. Without getting into too much of the details (yet), after investigation, it seems that a session is never created. I also tried getting the user off the using the CAS Assertion Thread Local Filter, but that was empty, too.
Any ideas?
My web.xml is below. Note that I'm running both ReportServer and the CAS server inside containers, so the casServerLoginUrl and casServerUrlPrefix params in the Authentication and Validation filters don't match but are correct.
Thanks again,
Shaun
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8080/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost/reportserver</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://cas:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost/reportserver</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>net.datenwerke.rs.ReportServerServiceConfig</listener-class>
</listener>
<session-config>
<session-timeout>240</session-timeout>
<!-- Disables URL-based sessions (no more 'jsessionid' in the URL using Tomcat) -->
<tracking-mode>COOKIE</tracking-mode>
</session-config>
</web-app>
Offline
Hi Shaun,
you are using the HttpServletRequestWrapperFilter, which should wrap the CAS user: getRemoteUser() should work.
I noticed some things:
1. I am not sure why are you setting "redirectAfterValidation" = false? The default is true. Why are you explicitly setting it to false ?
2. You say a session is not created: can you try setting "useSession" = true explicitly ? Like here: https://github.com/cas-projects/cas-sam … NF/web.xml
Please check the other settings as well.
3. You are using Cas20. Any specific reason for that? Maybe you can try with Cas30ProxyReceivingTicketValidationFilter as in the link. Further, here I read that a user seemed to solve a similar problem changing to Cas30ProxyReceivingTicketValidationFilter: https://stackoverflow.com/questions/383 … s-null-cas
4. I found a similar problem here: https://groups.google.com/forum/#!topic … s_7KLOhfhA
"I´ve solved it by going into the cas/services section and set "ignore attributes management via Tool" on both http and https.
As seen here https://wiki.jasig.org/display/CASUM/Attributes"
Please check this if it applies
5. If you validate the ticket more than once, you may have the same problem as here: https://groups.google.com/forum/#!topic … 9D6NlEFsLc
"Your CAS client is attempting to resuse a service ticket, or it’s submitting the same request twice. It validates ST-4 and about a minute later it attempts to validate it again. That won’t work.
Monitor traffic and see why you have two requests to validate the same ticket."
Can you check the logs and see if this is happening? ->
"In your sample app’s WEB-INF/classes, put a log4j file with DEBUG output enabled so you can observe the assertion that the client is receiving and other possible warning messages. Also, for your CAS server log configuration, enable DEBUG for the root logger and that will show you the assertion generated. The client log output will tell you if there is a clock drift and why."
6. Have you checked if *immediately* after authenticating the getRemoteUser() is available? Try to print it in the logs in the cas authentication service.
7. Other users seem to have SSL Certificate problems and getting getRemoteUser() null: :http://jasig.275507.n4.nabble.com/How-do-I-get-prinicpal-from-CASifyed-client-program-td265966.html
8. You may be mixing protocols, since you are using AuthenticationFilter and Cas20ProxyReceivingTicketValidationFilter: https://stackoverflow.com/questions/316 … t-from-cas
" org.jasig.cas.client.authentication.AuthenticationFilter and org.jasig.cas.client.validation.Cas10TicketValidationFilter (*or* org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter) "
I hope this points in the right direction. Anyhow, since this is a CAS specific problem, you can also ask in the CAS mailing lists:
https://groups.google.com/forum/#!forum/jasig-cas-user
Maybe they can help you more specifically.
Please tell us if this works.
Regards,
Eduardo
Offline
Thanks for sticking with me, Eduardo.
Whatever the problem I had was apparently not related to ReportServer or CAS. It must have been related to how I was running it. I decided I needed to eliminate as many variables as I could so I started over with a Bitnami Windows installation and our TEST CAS server (rather than the local one I was using). From there I was able to get everything going without any problems.
For anyone else using CAS and ReportServer, here's what I did.
1. Start with a existing ReportServer installation with an admin user that will match the CAS user you'll log in as.
2. Change the `web.xml` like the one I include below (make sure to change the CAS and server urls as needed).
3. Add your CustomPAM.groovy script (see mine below) in /fileserver/bin/onstartup.d.
4. Restart the tomcat server.
Note: For this simple test the CAS user has to already exist as a ReportServer user.
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://[CAS_SERVER]/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>https://[REPORT_SERVER]/reportserver</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://[CAS_SERVER]/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>https://[REPORT_SERVER]/reportserver</param-value>
</init-param>
</filter>
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Authentication Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>net.datenwerke.rs.ReportServerServiceConfig</listener-class>
</listener>
<!-- <servlet>
<servlet-name>Jersey REST Service</servlet-name>
<servlet-class>
com.sun.jersey.spi.container.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>net.datenwerke.rs.rest</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey REST Service</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping> -->
<session-config>
<session-timeout>240</session-timeout>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
<!-- Not for now <listener> <listener-class>net.datenwerke.rs.services.backgroundexecutor.ExecutorServiceProvider</listener-class>
</listener> -->
</web-app>
CustomPAM.groovy
package ldap
import javax.servlet.http.HttpServletRequest
import net.datenwerke.security.client.login.AuthToken
import net.datenwerke.security.service.authenticator.AuthenticationResult
import net.datenwerke.security.service.authenticator.ReportServerPAM
import net.datenwerke.security.service.authenticator.hooks.PAMHook
import net.datenwerke.security.service.usermanager.UserManagerService
import net.datenwerke.security.service.usermanager.entities.User
import com.google.inject.Inject
import com.google.inject.Provider
import org.apache.log4j.Logger
final CustomPAM CustomPAM = GLOBALS.injector.getInstance(CustomPAM.class);
GLOBALS.services.callbackRegistry.attachHook("CUSTOM_PAM", PAMHook.class, new PAMHook(){
public void beforeStaticPamConfig(LinkedHashSet<ReportServerPAM> pams){
}
public void afterStaticPamConfig(LinkedHashSet<ReportServerPAM> pams){
pams.clear();
pams.add(CustomPAM);
}
});
public class CustomPAM implements ReportServerPAM {
final static Logger logger = Logger.getLogger(CustomPAM.class);
private UserManagerService userManagerService;
private Provider<HttpServletRequest> httpRequest;
@Inject
public CustomPAM(UserManagerService userManagerService, Provider<HttpServletRequest> httpRequest) {
this.userManagerService = userManagerService;
this.httpRequest = httpRequest;
}
public AuthenticationResult authenticate(AuthToken[] tokens) {
String username = httpRequest.get().getRemoteUser();
User u = userManagerService.getUserByName(username);
if(null != u){
logger.info("####### CustomPAM: logging in : " + u.getUsername());
return new AuthenticationResult(true, u);
}
throw new Exception("Please contact your administrator to gain access to ReportServer.")
}
public String getClientModuleName() {
return null;
}
}
Thanks for all your help!
- Shaun
Offline
Hi Shaun,
I'm very glad everything works now!
Would it be ok for you if we use your (maybe modified) files in a blog entry we are planning regarding SSO ?
Best regards,
Eduardo
Offline
Sorry, I didn't notice your post until just now. Yes, you can use and modify those files, no problem.
Offline
Hello,
We try with the files given by sfblake.
1) we changed web.xml (we make sure to change the CAS and server urls as needed) --> when we restart reportserver, we obtain an error 404 (the requested resource [/reportserver] is not available)
2) we added CustomPAM.groovy script in /fileserver/bin/onstartup.d --> when we restart reportserver, we obtain the same error
3) we changed rs.authenticator.pams in reportserver.properties --> when we restart reportserver, we obtain the same error
we think that we have a problem with the web.xml but we don't find.
thanks
Last edited by Stéphane (2020-10-26 15:48:33)
Offline
Hi Stéphane,
what is the web.xml in your normal case (without CAS) and how exactly is the URL of your reportserver in this case ?
And after applying the CAS changes to web.xml -> what is your new web.xml?
Regards,
Eduardo
Offline
(we can continue this here: https://forum.reportserver.net/viewtopic.php?pid=7804)
Regards,
Eduardo
Offline