How to get CasToken from server? - java

I want to do a Cas Authentication from Standalone-Application but it fails on getting the Ticket from server. Can anyone provide me example code for a method that returns the ticket as String so i can use it for the Authentication. As you see the only Parameter should be the URL from the server. Thats waht i have yet(i know casToken is initialized on null an it doesnt work).
protected String getCasTicket(String serviceUrl) {
String casToken = null;
if (casToken == null){
logger.error("Failed to get CAS-Token!");
}else{
logger.info("Got CAS-Token successful!");
}
return casToken;
}

public class CasAuthenticationServlet extends HttpServlet {
...
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// NOTE: The CasAuthenticationToken can also be obtained using
// SecurityContextHolder.getContext().getAuthentication()
final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal();
// proxyTicket could be reused to make calls to the CAS service even if the
// target url differs
final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl);
// Make a remote call using the proxy ticket
final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8");
String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8");
...
}
CasAuthenticationProvider constructs a CasAuthenticationToken including the details contained in the TicketResponse and the GrantedAuthoritys.
Control then returns to CasAuthenticationFilter, which places the created CasAuthenticationToken in the security context.
Cas Example: https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/sample-apps.html#cas-sample
EDIT:
Please refer https://www.javaworld.com/article/3313114/what-is-a-java-servlet-request-handling-for-java-web-applications.html for creating a servlet

Related

Java Opensaml 3.4.6 : authnrequest subject is null - impossible to get user name

Developing a Java EE/JSF application, I am trying to include SAML sso functionality into it. Due to technical requirements (SAP BOBJ SDK) I need to use java 8, so I must stick with opensaml 3.x branch. As the application is some years old, I cannot add spring/spring-security to it just for SAML, that's why my code focuses on raw opensaml usage.
Mimicking the example code of this repository, I implemented the authentication basics:
This first code is called when I reach the "login" page. And send the AuthnRequest to my IDP
#Log4j2
#Named
public class SAMLAuthForWPBean implements Serializable {
private static final BasicParserPool PARSER_POOL = new BasicParserPool();
static {
PARSER_POOL.setMaxPoolSize(100);
PARSER_POOL.setCoalescing(true);
PARSER_POOL.setIgnoreComments(true);
PARSER_POOL.setIgnoreElementContentWhitespace(true);
PARSER_POOL.setNamespaceAware(true);
PARSER_POOL.setExpandEntityReferences(false);
PARSER_POOL.setXincludeAware(false);
final Map<String, Boolean> features = new HashMap<>();
features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE);
PARSER_POOL.setBuilderFeatures(features);
PARSER_POOL.setBuilderAttributes(new HashMap<>());
}
private String idpEndpoint = "url de azure por";
private String entityId = "glados";
private boolean isLogged;
#Inject
private LoginBean loginBean;
#Inject
private MainBean mainBean;
#Inject
private TechnicalConfigurationBean technicalConfigurationBean;
#PostConstruct
public void init() {
if (!PARSER_POOL.isInitialized()) {
try {
PARSER_POOL.initialize();
} catch (ComponentInitializationException e) {
LOGGER.error("Could not initialize parser pool", e);
}
}
XMLObjectProviderRegistry registry = new XMLObjectProviderRegistry();
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
registry.setParserPool(PARSER_POOL);
// forge auth endpoint
}
public boolean needLogon() {
return isLogged;
}
public void createRedirection(HttpServletRequest request, HttpServletResponse response)
throws MessageEncodingException,
ComponentInitializationException, ResolverException {
// see this link to build authnrequest with metadata https://blog.samlsecurity.com/2011/01/redirect-with-authnrequest-opensaml2.html
init();
AuthnRequest authnRequest;
authnRequest = OpenSAMLUtils.buildSAMLObject(AuthnRequest.class);
authnRequest.setIssueInstant(DateTime.now());
FilesystemMetadataResolver metadataResolver = new FilesystemMetadataResolver(new File("wp.metadata.xml"));
metadataResolver.setParserPool(PARSER_POOL);
metadataResolver.setRequireValidMetadata(true);
metadataResolver.setId(metadataResolver.getClass().getCanonicalName());
metadataResolver.initialize();
/*
* EntityDescriptor urlDescriptor = metadataResolver.resolveSingle( new CriteriaSet( new BindingCriterion(
* Arrays.asList("urn:oasis:names:tc:SAML:2.0:bindings:metadata"))));
*/
/*entityId = "https://192.168.50.102:8443/360.suite/loginSAML.xhtml";*/
entityId = "glados";
//idp endpoint, je pense => à obtenir des metadata
authnRequest.setDestination(idpEndpoint);
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
// app endpoint
authnRequest.setAssertionConsumerServiceURL("https://192.168.1.14:8443/360.suite/loginSAML.xhtml");
authnRequest.setID(OpenSAMLUtils.generateSecureRandomId());
authnRequest.setIssuer(buildIssuer());
authnRequest.setNameIDPolicy(buildNameIdPolicy());
MessageContext context = new MessageContext();
context.setMessage(authnRequest);
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
endpointContext.setEndpoint(URLToEndpoint("https://192.168.1.14:8443/360.suite/loginSAML.xhtml"));
VelocityEngine velocityEngine = new VelocityEngine();
velocityEngine.setProperty("resource.loader", "classpath");
velocityEngine.setProperty("classpath.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.init();
HTTPPostEncoder encoder = new HTTPPostEncoder();
encoder.setVelocityEngine(velocityEngine);
encoder.setMessageContext(context);
encoder.setHttpServletResponse(response);
encoder.initialize();
encoder.encode();
}
public String doSAMLLogon(HttpServletRequest request, HttpServletResponse response) {
isLogged = true;
technicalConfigurationBean.init();
return loginBean.generateSSOSession(request, technicalConfigurationBean.getSsoPreferences(),
new SamlSSO(technicalConfigurationBean.getCmsPreferences().getCms()));
}
private NameIDPolicy buildNameIdPolicy() {
NameIDPolicy nameIDPolicy = OpenSAMLUtils.buildSAMLObject(NameIDPolicy.class);
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat(NameIDType.TRANSIENT);
return nameIDPolicy;
}
private Endpoint URLToEndpoint(String URL) {
SingleSignOnService endpoint = OpenSAMLUtils.buildSAMLObject(SingleSignOnService.class);
endpoint.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
endpoint.setLocation(URL);
return endpoint;
}
private Issuer buildIssuer() {
Issuer issuer = OpenSAMLUtils.buildSAMLObject(Issuer.class);
issuer.setValue(entityId);
return issuer;
}
}
The redirect is successfully processed and the IDP sends back a POST request to my application that call this code :
#Override
public IEnterpriseSession logon(HttpServletRequest request) throws SDKException, Three60Exception {
HTTPPostDecoder decoder = new HTTPPostDecoder();
decoder.setHttpServletRequest(request);
AuthnRequest authnRequest;
try {
decoder.initialize();
decoder.decode();
MessageContext messageContext = decoder.getMessageContext();
authnRequest = (AuthnRequest) messageContext.getMessage();
OpenSAMLUtils.logSAMLObject(authnRequest);
// Here I Need the user
String user = authnRequest.getSubject().getNameID().getValue();
// BOBJ SDK
String secret = TrustedSso.getSecret();
ISessionMgr sm = CrystalEnterprise.getSessionMgr();
final ITrustedPrincipal trustedPrincipal = sm.createTrustedPrincipal(user, cms, secret);
return sm.logon(trustedPrincipal);
} catch (ComponentInitializationException | MessageDecodingException e) {
return null;
}
}
The issue here is that getSubject() is null on this query.
What did I miss here? Do I need to perform other requests? Do I need to add other configuration in my AuthnRequest?
As stated in the comment, I found the reason why my code was not working.
As I also asked this question on a french forum, can can find the translation of this answer here.
Short answer :
Opensaml knows where to send the authn request thanks to the SAMLPeerEntityContext. In my code I put my own application as the target of this request instead of using the idp HTTP-POST bind endpoint. Once this was changed, everything worked, the idp was answering back the SAMLResponse with proper name.
Long version
On my code, I was building the entity context like this :
SAMLPeerEntityContext peerEntityContext = context.getSubcontext(SAMLPeerEntityContext.class, true);
SAMLEndpointContext endpointContext = peerEntityContext.getSubcontext(SAMLEndpointContext.class, true);
endpointContext.setEndpoint(URLToEndpoint("https://192.168.1.14:8443/360.suite/loginSAML.xhtml"));
This code forces the authn request to be sent to my own application instead of the IDP. As this is the request, it cannot contain the identity.
If I replace this URL by idpEndpoint which I got from the IDP metadata file, the full workflow works as expected.
First something will not work as my IDP forces requests to be signed, so I need to add a signature part.
The "signing and verification" sample of this repository just works for that.
Then, as I need a real identity, I must NOT ask for a transient nameid. In my tests, UNSPECIFIED worked, but PERSISTENT should also make it.
Last, in the ACS receiver, I do NOT receive an authn request but a SAMLResponse with assertions. The code will therefore look like :
String userName =
((ResponseImpl) messageContext.getMessage()).getAssertions().get(0).getSubject().getNameID()
.getValue();
I simplified the code but one, of course, has to check that :
(((ResponseImpl)messageContext.getMessage()).getStatus() is SUCCESS
signatures are valid
assertions are properly populated
Thanks #identigral for your answer in the comment

SpringBoot embedded tomcat server reads unicode character in query parameter as null

I have a rest end point designed in spring boot. Tomcat is being used as embedded server. It takes a query parameter.
When I pass query parameter as param1%uFF07 tomcat internally reads parameter as null
When I pass query parameter as param1%FF07 tomcat reads as some character.
tomcat only reads '%' character when followed by two hexadecimal numbers, if u is placed after '%' character tomcat parse parameter as null with message
Character decoding failed. Parameter [name] with value [param1%uFF07]
has been ignored. Note that the name and value quoted here may be
corrupted due to the failed decoding. Use debug level logging to see
the original, non-corrupted values. Note: further occurrences of
Parameter errors will be logged at DEBUG level.
Here is spring boot controller code
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value = "name", required = false) String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
You are passing % sign in your url, but % is symbol in url, to pass % as it is... you will have to pass %25 then it will work as you expected.
So, if you pass %25uFF07 then it will show you %uFF07 as value.
No need to change anything in application.properties or any kind of settings. I have tested this in my project.
Please feel free to ask for any clarification. Hope It Helps.
I found out a way using filters. Basics about filters could be found over here. We can intercept request query string there and use Tomcat UDecoder class to parse the query string and if any exception is thrown we can show response of 400
public class SimpleFilter implements Filter {
private final UDecoder urlDecoder = new UDecoder();
private final Logger logger = LoggerFactory.getLogger(SimpleFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String queryString = httpServletRequest.getQueryString();
if (queryString != null) {
ByteChunk byteChunk = new ByteChunk();
byteChunk.setBytes(queryString.getBytes(), 0, queryString.length());
try {
urlDecoder.convert(byteChunk, true);
} catch (IOException ioException) {
logger.error("Hazarduos character found in request parameter.");
httpServletResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}

changing URL but want to send data to controller

My web application has a new requirement that if parameter coming in url then land to email page. otherwise on index page like always.
Its a very old client product and not much scope to change lot in code so i put a check in controller that if encrypted email coming in then land to email page.
example url -
http://localhost:8080/R2/Controller?email=jAOtTv22BfkTkVrhTN/RHQ==
Everything works fine but i want to change URL.
How can i get rid of " /Controller " in URL but still it hits to controller.???
Controller code like -
public class Controller extends HttpServlet {
static Logger logger = Logger.getLogger(Controller.class);
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
// get the action property from the request
String theAction = request.getParameter("action");
String theSource = request.getParameter("s");
String theSource1 = request.getParameter("email");
String em ="";
Action action=null;
em = EncryptEmail.decrypt(theSource1,GFWConstants.BLOWFISH_KEY);
if (em.equals(""))
rd = request.getRequestDispatcher("index.jsp?emailRtv=0");
else
rd = request.getRequestDispatcher("email-preferences.jsp?emailRtv=2&emailAddress="+em);
rd.forward(request,response);
return;
}
Thanks in advance.
adding two url-pattern to web.xml file worked.

Redirecting to a page using restful methods?

I've created a page which asks user to fill some form fields and when he submits, the form is sent to a Restful method which you can see below:
#POST
#Path("addUser")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
}
How can I redirect the user at the end of this function to (let's say) index.jsp?
change your code like this, the addUser() should return a Response Object
public Response addUser(#FormParam("username") String username,
#FormParam("password") String password,
#FormParam("id") String id,
#FormParam("group_name") String groupName,
#FormParam("authority_name") String authorityName,
#FormParam("authority_id") String authorityId
)
{
//Something will be done here
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
return Response.temporaryRedirect(location).build()
}
Create a URI using javax.ws.rs.core.UriBuilder that maps the parameters and other data you want to preserve. Then use Response.temporaryRedirect to return a redirect to the client and pass it the URI you’ve built.
Finally I come to this conclusion that there are no other way than what I did:
So here is my solution:
try {
java.net.URI location = new java.net.URI("../index.jsp?msg=A_User_Added");
throw new WebApplicationException(Response.temporaryRedirect(location).build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
By adding this block to my code, I got what I needed. Hope it helps you as well.
See below the usage of redirecting in web services:
public class LoginWebService {
#POST
#Path("/check")
public Response checkDetails(#FormParam("name") String name,#FormParam("pass") String pass ) throws URISyntaxException {
URI uri = new URI("/login/success");
URI uri2= new URI("http://localhost:9090/NewWebServiceproject/new/login/failure");
if(name.equals("admin") && pass.equals("pass"))
//#Path("http://localhost:8010/NewWebServiceproject/new/login/success");
{
return Response.temporaryRedirect(uri).build();
//Response.seeOther(uri);
//return Response.status(200).entity("user successfully login").build();
}
else
{
return Response.temporaryRedirect(uri2).build();
//Response.seeOther(uri2);
//return Response.status(200).entity("user logon failed").build();
}
}
#POST
#Path("/success")
public Response successpage()
{
return Response.status(200).entity("user successfully login").build();
}
#POST
#Path("/failure")
public Response failurepage()
{
return Response.status(200).entity("user logon failed").build();
}
}
It is not good idea to use the "WebApplicationException" in order to redirect the request. in Jersey (2.4.1) you should be able to redirect the request via the normal servlet way, (request.getServletContext().getRequestDispatcher().forward() or just response.sendRedirect())
The following is how Jersey process the request
org.glassfish.jersey.servlet.ServletContainer.service(HttpServletRequest request, HttpServletResponse response)
requestScope.runInScope
final ContainerResponse response = endpoint.apply(data)
methodHandler.invoke(resource, method, args);
Responder.process(ContainerResponse);
That methodHandler is your REST service class, method is the function in that service class.
The step to redirect page become straitforward
Get the (request, response) through Jersey injection (#Context HttpServletRequest request, #Context HttpServletResponse response) in class field or function parameter
Call request.getServletContext().getRequestDispatcher() to get the dispatcher for "forward"
or use Response.sendRedirect(url)
Once you application is returned (just null), Jersey will try to process the result in the "Responder.process(ContainerResponse)". In this step, it will use response to set status (204 no contents for your null return).
So the key point here is you must finalize/close response object before return from your service function. Otherwise, Jersey may overwrite your output.
Small tips on why "WebApplicationException" can overwrite Jersey repsponse. It is because org.glassfish.jersey.server.ServerRuntime.mapException() will use the "webApplicationException.getResponse()" as the return response result.

GateIn: a filter for the login servlet

I need to implement some logic before and after the login servlet invoked by my login.jsp.
So I wrote a filter for the url /login to do that. I need to get the user profile for some operations, so I created this LoginFilter class:
public class LoginFilter implements Filter {
private static Logger logger = Logger.getLogger(LoginFilter.class);
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String username = httpRequest.getParameter("username");
String password = httpRequest.getParameter("password");
chain.doFilter(request, response);
PortalRequestContext context = PortalRequestContext.getCurrentInstance();
if (context == null)
logger.info("PortalRequestContext is NULL");
else {
String userId = context.getRemoteUser();
if (userId == null || userId.equals(""))
logger.info("Login failed, IP:" + httpRequest.getRemoteAddr());
else
logger.info("Login executed, username:" + userId);
}
}
The problem is that "context" (PortalRequestContext) is always null. What ma I doing wrong? Is this the right approach?
If you are using GateIn, you can try using
org.exoplatform.portal.webui.util.Util.getPortalRequestContext().getRequest()
ce
You can develop a Valve and add it into Context file of "portal" webapp (Tomcat/conf/Catalina/localhost/portal.xml).
That's what is done in GateIN for SSO extension for example:
See ServletAccessValve
ServletAccess.setRequestAndResponse(request, response);
Then, the Request is accessed in SSOLoginModule using this:
// Tomcat way (Assumed that ServletAccessValve has been configured in context.xml)
else
{
request = ServletAccess.getRequest();
}
For JBoss, it's more simple, you have just to use
javax.security.jacc.PolicyContext.getContext(HttpServletRequest.class.getName())
At login time, PortalRequestContext's not been created yet, but you can get remote user by calling HttpServletRequest#getRemoteUser()
You can add a GateIN Filter like detailed here.
And you can use statically in this Filter the ConversationState to get the current username:
ConversationState.getCurrent().getIdentity().getUserId();
Just use the conversation state object:
// Gets the current user id
ConversationState conversationState = ConversationState.getCurrent();
org.exoplatform.services.security.Identity identity = conversationState.getIdentity();
String userId = identity.getUserId();

Categories