Shiro OAuth2 1.0 Manual
Transcripción
Shiro OAuth2 1.0 Manual
Shiro OAuth2 1.0 Manual Santos Zatarain Vera <[email protected]> INFTEL (http://inftel.mx) Monte Blanco 205 Lomas de Vista Hermosa Pachuca de Soto, Hidalgo, México CP 42090 19 de julio de 2013 Índice 1. Introducción 1.1. Shiro OAuth2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2. Registro con proveedores OAuth2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3. Entorno de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 2 2 2. Autenticación con Apache Shiro 2.1. pom.xml . . . . . . . . . . . 2.2. shiro.ini . . . . . . . . . . . 2.3. web.xml . . . . . . . . . . . 2.4. index.jsp . . . . . . . . . . . 2.5. login.jsp . . . . . . . . . . . 2.6. logout.jsp . . . . . . . . . . 2.7. protected.jsp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 3 4 4 5 5 6 6 3. Extendiendo Apache Shiro con un realm y un principal 3.1. MyPrincipal.java . . . . . . . . . . . . . . . . . . . 3.2. MyRealm.java . . . . . . . . . . . . . . . . . . . . . 3.3. users.properties . . . . . . . . . . . . . . . . . . . . 3.4. shiro.ini . . . . . . . . . . . . . . . . . . . . . . . . 3.5. protected.jsp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 7 7 9 9 9 4. Autenticando con Shiro OAuth2 4.1. MyHandler.java . . . . . . . 4.2. MyRealm.java . . . . . . . . 4.3. shiro.ini . . . . . . . . . . . 4.4. callback.jsp . . . . . . . . . 4.5. login.jsp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 10 10 12 12 13 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. ¡Es todo amigos! 1. 14 Introducción Este manual comprende el uso de Shiro OAuth2 y está dirigido a las personas interesadas en integrar OAuth2 a sus aplicaciones web. Para poder comprender los ejemplos es necesario tener conocimientos de Java, desarrollo de aplicaciones web, JSP, Apache Shiro y Maven. 1 1.1. Shiro OAuth2 Shiro OAuth2 es una serie de filtros para usarse dentro de Apache Shiro, los filtros fueron diseñados para ser sencillos de configurar y obtener resultados lo más rápido posible. Shiro OAuth2 emplea la estrategia de code authorization grant para obtener acceso a la información del perfil del usuario (resource owner) e iniciar el intento de login dentro de Apache Shiro. Visite http://oauth.net/2/ si desea conocer más detalles sobre OAuth2. 1.2. Registro con proveedores OAuth2 Para seguir los ejemplos, es necesario tener el registro correspondiente al proveedor de OAuth2. Durante el proceso de registro, será necesario conocer el redirect uri (o solo su dominio, según el proveedor). Ante un registro exitoso, el proveedor le proporcionará el client id y el client secret. Visite https://developers.google.com/accounts/docs/OAuth2 si utilizara Google Accounts para conocer más detalles. Visite http://msdn.microsoft.com/live/ff519582 si utilizara Live Connect para conocer más detalles. Visite https://developers.facebook.com/docs/facebook-login/ si utilizara Facebook Login para conocer más detalles. 1.3. Entorno de trabajo Todos los ejemplos aquí expuestos fueron realizados con Shiro OAuth2 1.0 Oracle Java Development Kit 7 update 25 NetBeans IDE 7.3.1 Maven 3.0.5 (incluido en NetBeans) Glassfish OSE 3.1.2.2 Apache Shiro 1.2.2 Visite http://inftel.mx/ si desea saber acerca de Shiro OAuth2. Visite http://java.oracle.com/ si desea saber acerca de Java Development Kit. Visite http://netbeans.org/ si desea saber acerca de NetBeans IDE. Visite http://maven.apache.org/ si desea saber acerca de Maven. Visite http://glassfish.net/ si desea saber acerca de Glassfish OSE. Visite http://shiro.apache.org/ si desea saber acerca de Apache Shiro. 2. Autenticación con Apache Shiro Este es el ejemplo de una aplicación web autenticando usuarios con Apache Shiro. Un ejemplo muy sencillo que cubre aspectos como configuración y manejo de sesión. La aplicación usa el standard layout de un proyecto Maven para aplicaciones web (WAR). 2 2.1. pom.xml pom.xml 1 2 3 å å 4 5 6 7 8 <groupId >example </ groupId > <artifactId >shiro - example </ artifactId > <version >1.0 - SNAPSHOT </ version > <packaging >war </ packaging > 9 10 <name >shiro - example </name > 11 12 13 14 <properties > <project .build. sourceEncoding >UTF -8</ project .build. sourceEncoding > </ properties > 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <dependencies > <dependency > <groupId >org. apache . shiro </ groupId > <artifactId >shiro -core </ artifactId > <version >1.2.2 </ version > </ dependency > <dependency > <groupId >org. apache . shiro </ groupId > <artifactId >shiro -web </ artifactId > <version >1.2.2 </ version > </ dependency > <dependency > <groupId >org.slf4j </ groupId > <artifactId >slf4j -api </ artifactId > <version >1.7.5 </ version > </ dependency > <dependency > <groupId >org.slf4j </ groupId > <artifactId >slf4j - jdk14 </ artifactId > <version >1.7.5 </ version > </ dependency > <dependency > <groupId >org.slf4j </ groupId > <artifactId >jcl -over - slf4j </ artifactId > <version >1.7.5 </ version > </ dependency > <dependency > <groupId >mx.com. inftel .oss </ groupId > <artifactId >shiro - oauth2 </ artifactId > <version >1.0 </ version > </ dependency > <dependency > <groupId >javax </ groupId > <artifactId >javaee -web -api </ artifactId > <version >6.0 </ version > <scope >provided </ scope > </ dependency > </ dependencies > 54 55 56 $ <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 > <build > <plugins > 3 $ <plugin > <groupId >org. apache . maven. plugins </ groupId > <artifactId >maven -compiler - plugin </ artifactId > <version >2.3.2 </ version > <configuration > <source >1.7 </ source > <target >1.7 </ target > </ configuration > </ plugin > <plugin > <groupId >org. apache . maven. plugins </ groupId > <artifactId >maven -war - plugin </ artifactId > <version >2.1.1 </ version > <configuration > <failOnMissingWebXml >false </ failOnMissingWebXml > </ configuration > </ plugin > </ plugins > </ build > 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 </ project > El archivo pom.xml contiene toda la información necesaria para construir el proyecto con Maven. 2.2. shiro.ini src/main/webapp/WEB-INF/shiro.ini 1 2 [main] authc . loginUrl = / login .jsp 3 4 5 [users ] administrator = verysecretpassword 6 7 8 9 [urls] /login .jsp = authc / protected .jsp = authc El archivo shiro.ini contiene la configuración utilizada por Apache Shiro para esta aplicación web. En este ejemplo la contraseña está guardada en texto plano con el formato username = password, una mala práctica de seguridad. En el ejemplo de Extendiendo Apache Shiro con un realm y un principal se muestra como usar contraseñas cifradas. 2.3. web.xml src/main/webapp/WEB-INF/web.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version ="1.0" encoding ="UTF -8"?> <web -app version ="3.0" xmlns =" http: // java.sun.com/xml/ns/ javaee " xmlns:xsi ="http: // www.w3. org /2001/ XMLSchema - instance " xsi:schemaLocation ="http: // java.sun.com/xml/ns/ javaee ␣ http: // java.sun.com/xml/ns/ javaee /web - app_3_0 .xsd"> <filter > <filter -name >ShiroFilter </filter -name > <filter - class >org. apache . shiro .web. servlet . ShiroFilter </filter -class > </ filter > <filter - mapping > <filter -name >ShiroFilter </filter -name > <url - pattern >/*</url - pattern > <dispatcher >REQUEST </ dispatcher > <dispatcher >FORWARD </ dispatcher > <dispatcher >INCLUDE </ dispatcher > <dispatcher >ERROR </ dispatcher > å å $ 4 $ 14 15 16 17 18 19 20 21 22 23 </filter - mapping > <listener > <listener - class >org. apache . shiro.web.env. EnvironmentLoaderListener </listener - class > </ listener > <session - config > <session - timeout > 30 </session - timeout > </session - config > </web -app > El archivo web.xml contiene toda la información necesaria para desplegar de forma correcta la aplicación web en el servlet/jsp container o servidor de aplicaciones. 2.4. index.jsp src/main/webapp/index.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 < %@page import ="org. apache . shiro . SecurityUtils " %> < %@page contentType ="text/html" pageEncoding ="UTF -8" %> <% boolean logged = SecurityUtils . getSubject (). isAuthenticated (); %> <! DOCTYPE HTML PUBLIC " -// W3C // DTD␣HTML␣4.01␣ Transitional //EN" "http :// www.w3.org/TR/html4 / loose.dtd"> <html > <head > <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Public Area </ title > </head > <body > <h1 >Public Area </h1 > <p>This area is accesible to all world </p> <p>By the way , you are <b>< %=(logged ? " LOGGED " : "NOT␣ LOGGED ") %></b></p> <p>Go to private area: <a href=" protected .jsp">protected .jsp </a></p> < %if ( logged ) { %> <p>Do logout : <a href=" logout .jsp">logout .jsp </a></p> < %} %> </body > </html > El archivo index.jsp representa a la parte pública de la aplicación, todos los usuarios (autenticados y no autenticados) pueden acceder a ella. 2.5. login.jsp src/main/webapp/login.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 < %@page import ="org. apache . shiro . SecurityUtils " %> < %@page contentType ="text/html" pageEncoding ="UTF -8" %> <% boolean logged = SecurityUtils . getSubject (). isAuthenticated (); if ( logged ) { response . sendRedirect (" index .jsp"); return ; } %> <! DOCTYPE HTML PUBLIC " -// W3C // DTD␣HTML␣4.01␣ Transitional //EN" "http :// www.w3.org/TR/html4 / loose.dtd"> <html > <head > 5 14 15 16 17 18 19 20 21 22 23 24 25 <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Login </ title > </head > <body > <h1 >Login </h1 > <form method ="POST"> Username : <input type="text" name=" username " value =" administrator "/> <br/> Password : <input type=" password " name=" password " value =" verysecretpassword "/> < br/> <input type=" submit "/> </form > </body > </html > å $ El archivo login.jsp contiene el formulario de inicio de sesión, el formulario es usado por los usuarios para iniciar sesión. En este ejemplo el formulario para iniciar sesión tiene valores por defecto en los campos, estos valores no deben estar asignados en entornos de producción. 2.6. logout.jsp src/main/webapp/logout.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 < %@page import ="org. apache . shiro . SecurityUtils " %> < %@page contentType ="text/html" pageEncoding ="UTF -8" %> <% SecurityUtils . getSubject (). logout (); response . sendRedirect (" index .jsp"); %> <! DOCTYPE HTML PUBLIC " -// W3C // DTD␣HTML␣4.01␣ Transitional //EN" "http :// www.w3.org/TR/html4 / loose.dtd"> <html > <head > <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Logout </ title > </head > <body > <h1 >Logout </h1 > </body > </html > El archivo logout.jsp simplemente termina la sesión del usuario. 2.7. protected.jsp src/main/webapp/protected.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 < %@page import ="org. apache . shiro . SecurityUtils " %> < %@page contentType ="text/html" pageEncoding ="UTF -8" %> <% String username = SecurityUtils . getSubject (). getPrincipal (). toString (); %> <! DOCTYPE HTML PUBLIC " -// W3C // DTD␣HTML␣4.01␣ Transitional //EN" "http :// www.w3.org/TR/html4 / loose.dtd"> <html > <head > <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Private Area </ title > </head > <body > <h1 >Private Area </h1 > <p>This area is only accesible to logged users </p> 6 16 17 18 19 20 <p>You are logged as <b>< %=username %></b></p> <p>Go to public area: <a href="index .jsp">index.jsp </a></p> <p>Do logout : <a href=" logout .jsp">logout .jsp </a></p> </body > </html > El archivo protected.jsp representa a la parte privada de la aplicación, solo los usuarios autenticados pueden acceder a ella. 3. Extendiendo Apache Shiro con un realm y un principal Este es el ejemplo de una aplicación web que usa la clase example.shiro.MyRealm para realizar la autenticación. Los usuarios son guardados en un archivo de propiedades con contraseñas cifradas y algunos datos adicionales. Este ejemplo esta basado en el ejemplo de la sección Autenticación con Apache Shiro. Modifica algunos archivos y agrega nuevos archivos. 3.1. MyPrincipal.java src/main/java/example/shiro/MyPrincipal.java 1 package example . shiro ; 2 3 import java.io. Serializable ; 4 5 public class MyPrincipal implements Serializable { 6 private String username ; private String fullname ; private String email ; 7 8 9 10 public MyPrincipal ( String username , String fullname , String email) { this. username = username ; this. fullname = fullname ; this.email = email ; } 11 12 13 14 15 16 public String getUsername () { return username ; } 17 18 19 20 public String getFullname () { return fullname ; } 21 22 23 24 public String getEmail () { return email; } 25 26 27 28 } Una instancia de la clase example.shiro.MyPrincipal es el principal que será agregado al subject durante la autenticación, contiene datos adicionales declarados en el resource example/shiro/users.properties. 3.2. MyRealm.java src/main/java/example/shiro/MyRealm.java 1 package example . shiro ; 2 3 import java.io. IOException ; 7 4 5 6 7 8 9 10 11 12 13 14 import import import import import import import import import import import java.io. InputStream ; java.util. Objects ; java.util. Properties ; org. apache . shiro. authc . AuthenticationException ; org. apache . shiro. authc . AuthenticationInfo ; org. apache . shiro. authc . AuthenticationToken ; org. apache . shiro. authc . SimpleAuthenticationInfo ; org. apache . shiro. authc . UnknownAccountException ; org. apache . shiro. authc . UsernamePasswordToken ; org. apache . shiro. authc .pam. UnsupportedTokenException ; org. apache . shiro. realm . AuthenticatingRealm ; 15 16 public class MyRealm extends AuthenticatingRealm { 17 private Properties users ; 18 19 public MyRealm () { this.users = new Properties (); try ( InputStream is = Objects . requireNonNull ( MyRealm . class . getResourceAsStream ("users. properties "))) { this.users .load(is); } catch ( IOException ex) { throw new RuntimeException (ex); } } 20 21 22 23 24 25 26 27 28 29 @Override protected AuthenticationInfo doGetAuthenticationInfo ( AuthenticationToken token ) throws AuthenticationException { if (! UsernamePasswordToken . class . isInstance (token)) { throw new UnsupportedTokenException (); } 30 31 32 33 34 35 36 UsernamePasswordToken upt = UsernamePasswordToken . class .cast(token); String fullnameKey = String . format (" %s. fullname ", upt. getUsername ()); String emailKey = String . format (" %s.email ", upt. getUsername ()); String passwordKey = String . format (" %s. password ", upt. getUsername ()); 37 38 39 40 41 String password = users . getProperty ( passwordKey ); if ( password == null || password . isEmpty ()) { throw new UnknownAccountException (); } 42 43 44 45 46 String fullname = users . getProperty ( fullnameKey ); if ( fullname == null || fullname . isEmpty ()) { fullname = "[no␣ fullname ]"; } String email = users . getProperty ( emailKey ); if (email == null || email . isEmpty ()) { email = "[no␣ email ]"; } 47 48 49 50 51 52 53 54 55 MyPrincipal myPrincipal = new MyPrincipal (upt. getUsername (), fullname , email); return new SimpleAuthenticationInfo ( myPrincipal , password , getName ()); 56 57 } 58 59 } La clase example.shiro.MyRealm implementa la interfaz org.apache.shiro.realm.Realm a través de la extensión de la clase org.apache.shiro.realm.AuthenticatingRealm. Visite http://shiro.apache.org/realm.html para conocer a detalle que es un realm. 8 3.3. users.properties src/main/resources/example/shiro/user.properties 1 2 3 administrator . password =$ shiro1 $SHA -256$500000$ QcjOAag93WPNk8TfDRdKEQ ==$ qDRFQbKgm151xa1GPVd9c / lWVtHPdnsXBNp1CjEBJGo = administrator . fullname = System administrator administrator .email=<email > å $ El resource example/shiro/user.properties contiene las contraseñas cifradas de los usuarios y algunos datos adicionales usados por la clase example.shiro.MyRealm. En este ejemplo la contraseña está cifrada en el formato shiro1 usando el algoritmo SHA-256 con 500000 iteraciones y generated salt (semilla generada). Este formato no es reversible, la contraseña usada en texto plano fue verysecretpassword. Visite http://shiro.apache.org/command-line-hasher.html para conocer los detalles necesarios para generar contraseñas cifradas. 3.4. shiro.ini src/main/webapp/WEB-INF/shiro.ini 1 2 [main] passwordService = org. apache . shiro . authc. credential . DefaultPasswordService 3 4 5 passwordMatcher = org. apache . shiro . authc. credential . PasswordMatcher passwordMatcher . passwordService = $ passwordService 6 7 8 myRealm = example . shiro. MyRealm myRealm . credentialsMatcher = $ passwordMatcher 9 10 authc . loginUrl = / login .jsp 11 12 13 14 [urls] /login .jsp = authc / protected .jsp = authc El archivo shiro.ini fue modificado para incluir una instancia de la clase example.shiro.MyRealm y las instancias de las clases necesarias para manejar contraseñas cifradas. 3.5. protected.jsp src/main/webapp/protected.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 < %@page import =" example . shiro . MyPrincipal " %> < %@page import ="org. apache . shiro . SecurityUtils " %> < %@page contentType ="text/html" pageEncoding ="UTF -8" %> <% MyPrincipal principal = ( MyPrincipal ) SecurityUtils . getSubject (). getPrincipal (); %> <! DOCTYPE HTML PUBLIC " -// W3C // DTD␣HTML␣4.01␣ Transitional //EN" "http :// www.w3.org/TR/html4 / loose.dtd"> <html > <head > <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Private Area </ title > </head > <body > <h1 >Private Area </h1 > <p>This area is only accesible to logged users </p> <p>You are logged as <b>< %=principal . getUsername () %></b></p> <p>Your fullname is <b>< %=principal . getFullname () %></b></p> 9 19 20 21 22 23 <p>Your email is <b>< %=principal . getEmail () %></b></p> <p>Go to public area: <a href="index .jsp">index.jsp </a></p> <p>Do logout : <a href=" logout .jsp">logout .jsp </a></p> </body > </html > El archivo protected.jsp fue modificado para mostrar los datos adicionales que ahora es posible saber a través del principal. 4. Autenticando con Shiro OAuth2 Este es el ejemplo de una aplicación web que usa Shiro OAuth2 para realizar la autenticación del usuario, en ningún momento se conoce la contraseña del usuario, solo se accede temporalmente al perfil de usuario dentro del proveedor OAuth2. Este ejemplo esta basado en el ejemplo de la sección Extendiendo Apache Shiro con un realm y un principal. Modifica algunos archivos y agrega nuevos archivos. 4.1. MyHandler.java src/main/java/example/shiro/MyHandler.java 1 package example . shiro ; 2 3 4 5 6 7 8 import import import import import import javax . servlet . ServletRequest ; javax . servlet . ServletResponse ; mx.com. inftel .shiro. oauth2 . AbstractOAuth2AuthenticatingFilter ; org. apache . shiro. authc . AuthenticationException ; org. apache . shiro. authc . AuthenticationToken ; org. apache . shiro. subject . Subject ; 9 10 11 12 public class MyHandler implements AbstractOAuth2AuthenticatingFilter . LoginSuccessHandler , AbstractOAuth2AuthenticatingFilter . LoginFailureHandler { 13 @Override public boolean onLoginSuccess ( AbstractOAuth2AuthenticatingFilter filter , AuthenticationToken token , Subject subject , ServletRequest request , ServletResponse response ) throws Exception { request . setAttribute (" oauth2 . token", token); return true; } 14 15 16 17 18 19 20 21 @Override public boolean onLoginFailure ( AbstractOAuth2AuthenticatingFilter filter , AuthenticationToken token , AuthenticationException e, ServletRequest request , ServletResponse response ) { request . setAttribute (" oauth2 . exception ", e); return true; } 22 23 24 25 26 27 28 29 } Esta clase ayuda a capturar el token usado durante la autenticación, si la autenticación fue exitosa. Si la autenticación falló, ayuda a capturar la exception que ocurrió durante la autenticación. En ambos casos deseamos que sea despachado el archivo callback.jsp, por lo tanto retornamos true en los métodos para continuar el procesamiento de la petición. 4.2. MyRealm.java src/main/java/example/shiro/MyRealm.java 1 package example . shiro ; 10 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import import import import import import import import import import import import import import java.io. IOException ; java.io. InputStream ; java.util. HashMap ; java.util.Map; java.util. Objects ; java.util. Properties ; mx.com. inftel .shiro. oauth2 . OAuth2AuthenticationToken ; org. apache . shiro. authc . AuthenticationException ; org. apache . shiro. authc . AuthenticationInfo ; org. apache . shiro. authc . AuthenticationToken ; org. apache . shiro. authc . SimpleAuthenticationInfo ; org. apache . shiro. authc . UnknownAccountException ; org. apache . shiro. authc .pam. UnsupportedTokenException ; org. apache . shiro. realm . AuthenticatingRealm ; 17 18 public class MyRealm extends AuthenticatingRealm { 19 20 21 private Properties users ; private Map <String , String > emails ; 22 23 24 25 26 27 28 29 30 public MyRealm () { this.users = new Properties (); try ( InputStream is = Objects . requireNonNull ( LiveConnectRealm . class . getResourceAsStream ("users. properties "))) { this.users .load(is); } catch ( IOException ex) { throw new RuntimeException (ex); } 31 this. emails = new HashMap < >(); for ( String key : users . stringPropertyNames ()) { if (key. endsWith (". email ")) { String username = key.split ("\\.", 2) [0]; String email = users . getProperty (key); this. emails .put(email , username ); } } 32 33 34 35 36 37 38 39 40 setAuthenticationTokenClass ( OAuth2AuthenticationToken . class ); 41 42 } 43 44 45 46 47 48 49 @Override protected AuthenticationInfo doGetAuthenticationInfo ( AuthenticationToken token ) throws AuthenticationException { if (! OAuth2AuthenticationToken . class . isInstance (token)) { throw new UnsupportedTokenException (); } 50 51 52 53 54 55 OAuth2AuthenticationToken authToken = OAuth2AuthenticationToken . class .cast(token ); String username = emails .get( authToken . getCredentials ()); if ( username == null || username . isEmpty ()) { throw new UnknownAccountException (); } 56 57 58 String fullnameKey = String . format (" %s. fullname ", username ); String emailKey = String . format (" %s.email ", username ); 59 60 61 62 63 String fullname = users . getProperty ( fullnameKey ); if ( fullname == null || fullname . isEmpty ()) { fullname = "[no␣ fullname ]"; } 11 String email = users . getProperty ( emailKey ); if (email == null || email . isEmpty ()) { email = "[no␣ email ]"; } 64 65 66 67 68 MyPrincipal myPrincipal = new MyPrincipal (username , fullname , email); return new SimpleAuthenticationInfo ( myPrincipal , email , getName ()); 69 70 } 71 72 } Este clase implementa el realm utilizado durante la autenticación. Es muy similar al ejemplo presentado en la sección Extendiendo Apache Shiro con un realm y un principal, pero durante la creación del objeto org.apache.shiro.authc.SimpleAuthenticationInfo no usa una contraseña cifrada como credentials, usa el correo electrónico registrado en el sistema. 4.3. shiro.ini src/main/webapp/WEB-INF/shiro.ini 1 2 [main] myRealm = example . shiro. MyRealm 3 4 myHandler = example .shiro . MyHandler 5 6 7 8 9 10 11 12 13 14 # oauth2 = mx.com. inftel . shiro . oauth2 . GoogleAccountsAuthenticatingFilter oauth2 = mx.com. inftel .shiro . oauth2 . LiveConnetAuthenticatingFilter # oauth2 = mx.com. inftel . shiro . oauth2 . FacebookLoginAuthenticatingFilter oauth2 . loginSuccessHandler = $ myHandler oauth2 . loginFailureHandler = $ myHandler oauth2 . loginUrl = / callback .jsp oauth2 . redirectUri = http <s>:// <sever >:<port >/<context root >/ callback .jsp oauth2 . clientId = <client id > oauth2 . clientSecret = <client secret > 15 16 authc . loginUrl = / login .jsp 17 18 19 20 21 [urls] / callback .jsp = oauth2 /login .jsp = authc / protected .jsp = authc El archivo shiro.ini fue modificado para crear una instancia de la clase mx.com.inftel.shiro.oauth2.LiveConnetAuthenticatingFilter, esta última instancia es el filtro que realiza la mayor parte del trabajo de autenticación. En el ejemplo se incluyen como comentario las clases mx.com.inftel.shiro.oauth2.GoogleAccountsAuthenticatingFilter y mx.com.inftel.shiro.oauth2.FacebookLoginAuthenticatingFilter, en esta configuración solo se puede usar al mismo tiempo un solo proveedor por ruta. Algunos proveedores OAuth2 no permiten usar localhost como <server> en la propiedad redirectUri, tal vez se requiera modificar el archivo hosts para agregar un alias del localhost. 4.4. callback.jsp src/main/webapp/callback.jsp 1 2 3 4 5 6 < %@page < %@page < %@page < %@page < %@page <% import ="mx.com. inftel . shiro . oauth2 . OAuth2AuthenticationToken " %> import ="org. apache . shiro . authc . AuthenticationException " %> import ="org. apache . shiro . authc . AuthenticationToken " %> import ="org. apache . shiro . SecurityUtils " %> contentType ="text/html" pageEncoding ="UTF -8" %> 12 boolean logged = SecurityUtils . getSubject (). isAuthenticated (); AuthenticationToken authToken = ( AuthenticationToken ) request . getAttribute (" oauth2 . token"); AuthenticationException authException = ( AuthenticationException ) request . getAttribute ( " oauth2 . exception "); OAuth2AuthenticationToken oauth2Token ; if ( authToken instanceof OAuth2AuthenticationToken ) { oauth2Token = ( OAuth2AuthenticationToken ) authToken ; } else { oauth2Token = null; } 7 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 $ å å 8 %> <! DOCTYPE html > <html > <head > <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Shiro OAuth2 </ title > </head > <body > <h1 >Shiro OAuth2 </h1 > <p>You are <b>< %=(logged ? "" : "NOT") %> LOGGED </b></p> <p>Go to public area: <a href="index .jsp">index.jsp </a></p> <p>Go to private area: <a href=" protected .jsp">protected .jsp </a></p> <p>A login attemp was <b>< %=(authToken != null || authException != null ? "" : "NOT ") %> MADE </b></p> < %if ( authException != null) { %> <p>There was an error during login attemp : <b>< %=authException . getClass (). getName () %></b></p> < %} %> < %if ( oauth2Token != null) { %> <p>Your profile data is:</p> <pre >< %=oauth2Token . getPrincipal (). toString (4) %></pre > < %} %> </body > </html > å å $ $ $ Este archivo muestra el token o la exception capturados durante la autenticación, según sea el caso de una autenticación exitosa o una autenticación fallida. 4.5. login.jsp src/main/webapp/login.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 < %@page import ="org. apache . shiro . SecurityUtils " %> < %@page contentType ="text/html" pageEncoding ="UTF -8" %> <% boolean logged = SecurityUtils . getSubject (). isAuthenticated (); if ( logged ) { response . sendRedirect (" index .jsp"); return ; } %> <! DOCTYPE HTML PUBLIC " -// W3C // DTD␣HTML␣4.01␣ Transitional //EN" "http :// www.w3.org/TR/html4 / loose.dtd"> <html > <head > <meta http -equiv ="Content -Type" content ="text/html;␣ charset =UTF -8"> <title >Login </ title > </head > <body > <h1 >Login </h1 > 13 19 20 21 <p>Login with Shiro OAuth2 : <a href=" callback .jsp">callback .jsp </a></p> </body > </html > Este archivo solo tiene un enlace a callback.jsp, ruta donde el filtro de autenticación para Shiro OAuth2 realiza su trabajo. No existe formulario para autenticación tradicional porque en el archivo shiro.ini solo esta configurada la autenticación con Shiro OAuth2. 5. ¡Es todo amigos! Espero que este pequeño manual sea de gran ayuda. Sé que faltan detalles por explicar y algunas explicaciones son claras para mi, pero no para los demás. Intentaré ir mejorando este manual con la ayuda de todos. Para terminar, un pequeño comercial: El equipo de INFTEL (http://inftel.mx) es una pequeña familia de programadores, nuestro principal giro es el desarrollo de Sistemas de Información y Aplicaciones Empresariales a la medida, nos ubicamos en la ciudad de Pachuca de Soto, Hidalgo, México también conocida como La Bella Airosa. 14