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

Documentos relacionados