Criando um portlet de login customizado para IBM WebSphere Portal (Passo a Passo)

Este artigo tem o objetivo de demonstrar uma das maneiras de extender as funcionaliades do portlet de login padrão do IBM WebSphere Portal. De forma prática, iremos criar passo a passo um portlet de Login para o portal incluindo duas solicitações muito comuns, Captcha e Redirecionamento.

Rodrigo Alves dos Reis, IT Specialist & Application Architect

Rodrigo ReisRodrigo Reis

Rodrigo Reis é IT Specialist e Application Architect em IBM Collaboration Solutions. Possui 15 anos de experiência em tecnologias IBM. Recentemente, passou a fazer parte da equipe de consultoria para clientes de alto perfil para grandes implementações de soluções de colaboração. Antes disso, atuou como Team Leader e Application Architect durante 6 anos em projeto de missão crítica internacional. Também é especialista no desenvolvimento de sistemas para dispositivos móveis. Rodrigo é natural de Salvador, Bahia, Brazil e Bacharel em Ciência da Computação pela Faculdade Ruy Barbosa. Perfil My Developer Works



23/Jul/2013

Ferramentas utilizadas:

- IBM Rational Application Developer 8.0.0.4
- IBM WebSphere Portal 8.0.0.1

Introdução

O WebSphere Portal disponibiliza muitas maneiras de configurar a segurança de autenticação. Contudo é comum a necessidade de estender as funcionalidades de login, logout ou controle de sessão. Neste artigo iremos explorar uma das APIs de segurança do WebSphere Portal para criar, passo a passo, um novo portlet de login e incluir uma nova funcionalidade.


LoginService

A interface publica LoginService (com.ibm.portal.portlet.service.login.LoginService) pode ser utilizada juntamente com um formulário de login em qualquer portlet para autenticar usuários no portal. Esse serviço é muito útil em diferentes cenários, como por exemplo:

1 - melhorar a apresentação do portlet de login ou adicionando informações sobre política de segurança;
2 - adicionar novos campos de entrada, como departamento ou cidade, por exemplo;
3 - incluir novas funcionalidades.

Para explorar esse serviço, vamos criar um exemplo de portlet de login abrangendo o segundo e terceiro cenário citados acima. Neste portlet vamos criar uma solução de CAPTCHA extremamente simples para verificar se trata-se de um humano tentando logar no Portal. Para isso vamos criar uma servlet dentro de nosso portlet que irá gerar um CAPTCHA de 6 dígitos usando letras e números. Esse servlet retornará uma imagem contendo o código que deverá ser confirmado dentro de um campo no formulário de login. A próxima seção descreve como criar esse portlet passo a passo.


Criando o Portlet

1 – Criar um novo projeto no RAD

Clique em File > New > Other (Ctrl + N) e localize a pasta Portal. Dentro desta pasta escolha Portlet Project e clique em Next, conforme tela abaixo.

Observação: Caso não possa ver a pasta Portal e Portlet Project na tela acima é porque essas opções não foram instaladas junto com o RAD. Use o IBM Installation Manager para modificar a instalação e adicionar estes recursos.

Preencha as opções conforme a tela a seguir e clique em Next.

Na próxima tela não é necessária nenhuma alteração, clique em Next.

Na tela abaixo desmarque a primeira opção conforme ilustrado e clique em Finish.

Após a criação do projeto você terá a seguinte estrutura de projeto no workspace.

2 – Copiar bibliotecas necessárias para auxiliar a codificação

Crie uma pasta "lib" dentro do projeto e copie os arquivos wp.auth.base.jar e wp.auth.cmd.jar. Esses arquivos se encontram em <PortalServerRoot>\base nas pastas a seguir:

- wp.auth.base\shared\app
- wp.auth.cmd\shared\app

Observação: Não adicione os .jar na pasta WebContent \ WEB-INF \ lib. Pois os arquivos serão incluídos automaticamente no build e irá gerar exceções na tentativa de login.

Clique com o botão direito a raiz do projeto e Build Path > Configure Build Path...

Na tab Libraries clique no botão Add JARs. Na nova janela expanda o projeto CustomLogin, selecione os dois arquivos dentro da pasta lib e clique em OK.

Clique em OK para finalizar.

3 – Modificar o arquivo CustomLoginPortlet.java

O portlet de login irá utilizar uma instância da classe LoginService para efetuar a autenticação do usuário. Para acessa-la utilizaremos a interface LoginHome que pode ser recuperada via JNDI. Como JNDI é uma operação custosa, iremos inserir sua chamada na inicialização do portlet (método init) e armazenar o PortletServiceHome em uma variável de instância.

Variavéis de instância:

public static String ERROR_KEY = "error";
private static LoginHome loginHome = null;

Método de inicialização do portlet:

public void init() throws PortletException {
        super.init();
        try {
            PortletServiceHomepsh;
            Contextctx = new InitialContext();
            
            psh = (PortletServiceHome) ctx.lookup(LoginHome.JNDI_NAME);
            loginHome = (LoginHome) psh.getPortletService(LoginHome.class);
        
        }   catch (NamingException e) {
             throw new PortletException("The Service is not available", e);
        }

}

No método doLogin iremos recuperar os valores de usuário e senha digitados pelo usuário na página JSP e utilizar o método login da classe LoginService para autenticar o usuário. O método de login inclui um conjunto de exceções para tratar as diferentes condições de erro que podem causar falha no login e devem ser tratadas.

Método que irá processar o login :

public void doLogin(ActionRequestrequest, ActionResponse response) {

String userid = request.getParameter("UserId");
if (userid == null || userid.equals("")) {
	response.setRenderParameter(ERROR_KEY, "Please, type the User ID");
	return;
}

String password = request.getParameter("Password");
if (password == null || password.equals("")) {
	response.setRenderParameter(ERROR_KEY, "Please, type the Password");
	return;
}

Map<?, ?>loginContext = Collections.EMPTY_MAP;
LoginServiceloginService = (LoginService) loginHome.getLoginService(
request, response);

try {
	loginService.login(userid, password.toCharArray(), loginContext, null);
} catch (javax.security.auth.login.LoginException e) {
	response.setRenderParameter(ERROR_KEY,
	"Login failure: " + e.getMessage());
} catch (WSSecurityException e) {
	response.setRenderParameter(ERROR_KEY,
	"Login failure: " + e.getMessage());
} catch (PasswordInvalidException e) {
	response.setRenderParameter(ERROR_KEY, "Invalid password");
} catch (UserIDInvalidException e) {
	response.setRenderParameter(ERROR_KEY, "Invalid User ID");
} catch (AuthenticationException e) {
	response.setRenderParameter(ERROR_KEY, "Authentication Failed: "
      + e.getMessage());
} catch (UserAlreadyLoggedInException e) {
	response.setRenderParameter(ERROR_KEY, "User already authenticated: " + 
e.getMessage());
} catch (LoginException e) {
	response.setRenderParameter(ERROR_KEY,
	"Login failure: " + e.getMessage());
}

}

Modifique o método processAction() conforme abaixo:

public void processAction(ActionRequestrequest, ActionResponse response)
		throws PortletException, java.io.IOException {

		doLogin(request,response);	
	}

4 – Modificar o arquivo CustomLoginPortletView.jsp

Na página JSP iremos adicionar os campos para receber o usuário e senha, código para exibir mensagem de erro e botão de login.

Página que será exibida ao usuário:

<%String error = renderRequest.getParameter(CustomLoginPortlet.ERROR_KEY);
if (error != null) {
%><h4>Erro: <%=error%></h4>
<% } %>

<div>
<formaction='<portlet:actionURL></portlet:actionURL>'method="POST">
<table>
<tr>
<td>User ID:</td>
<td><inputtype="text"name="UserId"/></td>
</tr>
<tr>
<td>Password:</td>
<td><inputtype="password"name="Password"></td>
</tr>
<tr>
<tdcolspan="2"><inputtype="submit"value="Login"/></td>
</tr>
</table>
</form>
</div>

5 – Exportar CustomLogin portlet

Para instalar o portlet iremos exporta-lo como arquivo WAR. Clique em File > Export. Na nova janela navegue até a pasta Web e escolha WAR file e clique em Next.

Na janela de Export escolha o projeto CustomLogin no campo Web project e informe o caminho onde o arquivo será salvo em Destination. Para finalizar clique em Finish.

6 – Instalar CustomLogin portlet

Efetue login como Administrator do Portal e abra a página de Administração.

Navegue até Portlet Management > Web Modules.

Clique no botão Install.

Na página seguinte clique em Browse... para localizar o arquivo WAR criado no último passo e então clique em Next.

Confirme os valores Enterprise Application display name e Context root e clique em Finish.

7 – Definir acesso ao portlet

Para que os usuários do Portal possam ter acesso ao novo portlet de login precisamos definir o acesso para usuários anônimos e autenticados.

Ainda dentro da administração do WebSphere Portal, navegue até Portlet Management >Portlets.

Certifique-se que no campo Search by está selecionado Title starts with e no campo Search digite: CustomLogin

Clique no botão Search.

Na lista de resultados irá aparecer o portlet. Clique no ícone representado por uma chave para definir o acesso ao portlet.

Clique no botão com ícone representado por um Lápis na opção User.

Clique no botão Add e selecione All Authenticated Portal Users e Anonymous Portal User em seguida clique em OK.

De volta a tela anterior clique em CustomLogin. Para finalizar clique em Done.

8 – Adicionar o portlet CustomLogin à página de login

Para testar o novo portlet de login iremos adiciona-lo na página oculta wps.Login.

Dentro da Administração do Portal navegue até Portal User Interface >ManagePages.

Certifique-se que no campo Search by está selecionado Title starts with e no campo Search digite: Login

Observação: Antes de modificar a página é recomendado exporta-la para poder restaurar posteriormente em caso de algum problema. Para isso clique no botão de exportação e salve o xml.

Clique no ícone para editar a página, representado por um lápis.

Clique no botão Add Portlets, procure por CustomLogin, selecione e clique em OK.

Clique em Done para finalizar.

Ao final do processo teremos dois portlets de login nesta página. Utilizaremos o novo portlet para fazer a autenticação.

Efetue o Log Out e teste o novo portlet.

Observação: É recomendado não remover o portletLogin padrão do Portal enquanto não estiver certo que o novo portlet de login está completamente funcional. Pois caso o seu portlet não esteja funcionando corretamente, não será possível autenticar no portal até restaurar a página de login usando xmlaccess.

9 – Adicionado novas funcionalidades ao portlet

Até aqui temos um portlet de login similar ao padrão do WebSphere Portal. Agora iremos incluir uma nova funcionalidade.

9.1 - Crie uma nova classe chamada CaptchaServlet.

No Rational Application Developer, clique em File > New > Other (Ctrl + N) e localize a pasta Web. Dentro desta pasta escolha Servlet e clique em Next.

Preencha as opções conforme a tela a seguir e clique em Next.

Na próxima tela, em URL mappings, clique no /CaptchaServlet e em seguida no botão Edit.

Digite /captcha-image.jpg e clique em OK. Clique em Finish.

9.2 – Modificar o arquivo CaptchaSevlet.java

O método doGet do servlet que irá criar o código captcha e retorna uma imagem está listado a seguir.

protectedvoid doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {
		response.setContentType("image/jpg");

		//number of characters and size of captcha image
		int totalChars = 6;
		int height = 38;
		int width = 148;

		//font styles
		Font fontStyle1 = new Font("Arial", Font.BOLD, 28);
		Font fontStyle2 = new Font("Verdana", Font.BOLD, 26);

		//random characters in the image
		Random randomChars = new Random();
		String abs = Long.toString(Math.abs(randomChars.nextLong()), 36);
		String imageCode = abs.substring(0, totalChars);

		//BufferedImage is used to create a create new image
		BufferedImage biImage = new BufferedImage(width, height, 1);
		Graphics2D g2dImage = (Graphics2D) biImage.getGraphics();

		for (int i = 0; i < totalChars; i++) {
			int r = randomChars.nextInt(255);
			int g = randomChars.nextInt(255);
			int b = randomChars.nextInt(255);
			
			String str = imageCode.substring(i, i + 1);
			
			g2dImage.setColor(new Color(r, g, b));
					
			if (i % 2 == 0) {
				g2dImage.setFont(fontStyle1);				
				g2dImage.drawString(str, 25 * i, 24);
			} else {
				g2dImage.setFont(fontStyle2);
				g2dImage.drawString(str, 25 * i, 35);
			}
		}

		//create jpeg image and write to the page
		OutputStream osImage = response.getOutputStream();
		ImageIO.write(biImage, "jpeg", osImage);

		//dispose function is used destroy an image object
		g2dImage.dispose();

		getServletContext().setAttribute("securityCode", imageCode);	
	}

9.3 – Modificar o arquivo CustomLoginPortletView.jsp

Adicionar as linhas a seguir antes do botão submit para incluir um campo onde será digitado o código captcha e a imagem retornada pelo servlet ao lado.

<tr>
<td>Código de verificação:</td>
<td>
	<input type="text" name="Captcha"/>
	<img src="<%=renderResponse.encodeURL(renderRequest.getContextPath()
		+ "/captcha-image.jpg")%>"/>
</td>
</tr>

9.4 – Modificar o arquivo CustomLoginPortlet.java

Para incluir a verificação do código captcha, adcionar o código abaixo no método processAction antes da declaração da variável loginService.

String captcha = request.getParameter("Captcha");
if (captcha == null || captcha.equals("")) {
String errMsg = "Please type the sequence of characters in the image";
response.setRenderParameter(ERROR_KEY, errMsg);
return;
}

PortletContext ctx = request.getPortletSession().getPortletContext();
String secureCode = 
(String)PortletUtils.getServletContext(ctx).getAttribute("securityCode");
if (!captcha.equals(secureCode)) {
String errMsg = "Please type the correct sequence of characters in the image";
response.setRenderParameter(ERROR_KEY, errMsg);
return;
}

Exporte novamente o WAR e atualize o portlet em Portlet Management > Web Modules.

Observação: Conforme expressado anteriormente, essa é uma solução de CAPTCHA extremamente simples que pode vir a ser violada. Uma solução mais completa e difícil de ser quebrada não faz parte do escopo deste artigo. Não recomendamos o uso deste código em ambiente de produção.

Ao final de todo processo você deve ter um portlet similar ao exibido a seguir.

10 - Redirecionando usuário para uma página personalizada

Algumas vezes quando criando um portlet de login customizado você pode se deparar com demandas de redirecionamento após o login como: redirecionar todos usuários para uma página especifica, redirecionar usuário para páginas diferentes de acordo com seu departamento ou redirecionar usuário para uma página pedindo para alterar senha. Para cada um destes cenários existem diferentes opções de implementação. A seguir será mostrada uma maneira simples de redirecionar todos usuários do portal para uma página especifica.

10.1 – Modificando comportamento no portlet de login

O comportamento padrão do serviço LoginService é:

- Se não existir erro no login, usuário é autenticado e redirecionado para página protegida (Home page do portal)
- Se um erro ocorre no login, o portlet exibe o formulário de login novamente

Iremos modificar o portlet para incluir o seguinte comportamento:

- Executar o login em um método que vai retornar Verdadeiro se o usuário foi autenticado com sucesso e Falso caso o contrário
- Quando Verdadeiro, redirecionar para página customizada

Na classe CustomLoginPortlet modifique o método doLogin() incluindo as modificações abaixo.

public boolean doLogin(ActionRequestrequest, ActionResponse response) {

        String userid = request.getParameter("UserId");
        if (userid == null || userid.equals("")) {
        response.setRenderParameter(ERROR_KEY, "Please type the User id");
        return false;
        }       
        
        String password = request.getParameter("Password");
        if (password == null || password.equals("")) {
        response.setRenderParameter(ERROR_KEY, "Please type the password");
        returnfalse;
        }        
        
        String captcha = request.getParameter("Captcha");
        if (captcha == null || captcha.equals("")) {
        response.setRenderParameter(ERROR_KEY, "Please type the sequence of characters 
        in the image");
        return false;
         }      
        
        //Use servlet context to get session variable
        PortletContextctx = request.getPortletSession().getPortletContext();
        String secureCode = 
        (String)PortletUtils.getServletContext(ctx).getAttribute("securityCode");
        if (!captcha.equals(secureCode)) {
        response.setRenderParameter(ERROR_KEY, "Please type the correct sequence 
        of characters in the image");
        return false;
         }       
        
        LoginService loginService = (LoginService) loginHome.getLoginService(
        request, response);
        
        Map<?, ?>loginContext = Collections.EMPTY_MAP;
        try {
        //Invoked to trigger a login to the portal
        loginService.login(userid, password.toCharArray(), loginContext, null);
        return true;
                } catch (javax.security.auth.login.LoginException e) {
        response.setRenderParameter(ERROR_KEY,
        "WAS login failed: " + e.getMessage());
        return false;
                } catch (WSSecurityException e) {
        response.setRenderParameter(ERROR_KEY,
        "WAS login failed: " + e.getMessage());
        return false;
                } catch (PasswordInvalidException e) {
        response.setRenderParameter(ERROR_KEY, "Password invalid");
        return false;
                } catch (UserIDInvalidException e) {
        response.setRenderParameter(ERROR_KEY, "User id invalid");
        return false;
                } catch (AuthenticationException e) {
        response.setRenderParameter(ERROR_KEY, "Authentication failed: "
                           + e.getMessage());
        return false;
                } catch (UserAlreadyLoggedInException e) {
        response.setRenderParameter(ERROR_KEY, "User already logged in: "
                            + e.getMessage());
        return false;
                } catch (LoginException e) {
        response.setRenderParameter(ERROR_KEY,
        "Login failed: " + e.getMessage());
        return false;
                }
        
}

Modifique o método processAction() conforme abaixo:

public void processAction(ActionRequestrequest, ActionResponse response) throws 
PortletException, java.io.IOException {

		boolean isLogged = doLogin(request,response);

		if(isLogged == true)
		     response.sendRedirect("/wps/myportal/custompage");

    }

Gere um novo WAR e faça o deploy do portlet para validar as alterações, conforme passo previamente realizado nesse artigo

10.2 – Outras opções de redirecionamento

Conforme mencionado anteriormente a solução acima é simples e para todos usuários do portal. Desde a versão 6.1 o WebSphere Portal prover serviços de redirecionamento mais sofisticados através de AutenticationFilters. Esses recursos permitem trabalhar com login, logout, session timeout e possibilitam atender as questões mais complexas de redirecionamento. Para maiores informações consulte o Information Center do WebSphere Portal e procure por "AutenticationFilters".


Recursos

Centro de Informações do IBM WebSphere Portal e IBM Web Content Manager

WebSphere no IBM developerWorks

IBM WebSphere Portal API e SPI

IBM WebSphere Redbooks

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=WebSphere
ArticleID=937256
ArticleTitle=Criando um portlet de login customizado para IBM WebSphere Portal (Passo a Passo)
publish-date=07232013