mboost-dp1

JSF read-response og fejlbeskeder til brugeren


Gå til bund
Gravatar #1 - Windcape
30. mar. 2009 14:05
Hej

I forbindelse med en klassisk "login" side skal jeg udskrive en fejlbesked til brugeren hvis informationerne er forkerte.

Det er gjort med følgende test-setup


<faces-config>
<managed-bean>
<managed-bean-name>Login</managed-bean-name>
<managed-bean-class>website.Login</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<navigation-rule>
<display-name>Index</display-name>
<from-view-id>/WEB-INF/index.jsp</from-view-id>
<navigation-case>
<from-action>#{SimpleLogin.Login}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/WEB-INF/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>


og med Login metode


public String login()
{
if(username == "test" && password.equals("test"))
{
return "success";
}

return "failure";
}


Hvad jeg så ikke kan finde ud af er hvordan jeg kan aflæse "failure" værdien fra min view (login.jsp) så jeg kan vise en custom fejlbesked (ikke JSF validators, de er horrible).

Jeg skal så på linje 20 af følgende havde indsat min fejlbesked, men kan ikke se hvordan det kan gøres på en logisk smart og elegant måde.


<%@ page language="java" contentType="text/html" pageEncoding="UTF-8"
%><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"
%><%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"
%><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<head>
<title>MyWebsite</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="media/style.css" media="all" />
</head>
<body>
<h1>MyWebsite</h1>
<f:view>
<h:form id="login">
<fieldset style="width: 280px;">
<legend>Login</legend>
<!-- Skal have en fejlbesked her, i formattet <div class="error">FEJL</div> -->
<p>
<label>Username</label>
<h:inputText styleClass="field" id="username" required="true" />
</p>
<p>
<label>Password</label>
<h:inputSecret styleClass="field" id="password" required="true" />
</p>
<p>
<h:commandButton action="#{Login.login}" value="Login" />
</p>
</fieldset>
</h:form>
</f:view>
</body>
</html>
Gravatar #2 - arne_v
30. mar. 2009 14:51
#1

Hvis jeg har forstået dig ret, så vil du have noget conditional som ikke er JSF men som virker sammen med JSF.

Som f.eks. et JSTL if tag.

Det burde kunne bruges her.
Gravatar #3 - Windcape
30. mar. 2009 15:19
#2

Det ideele ville jo være at definere fejlbeskeden fra serveren, ie. fra min Backed Bean.

Men som jeg ser det er en Bean aldrig klar over at der er foregået et request, og kan derfor ikke teste på responsedata.

Pointen er at jeg nægter at redirecte brugeren til en anden side bare for at skrive en fejlmeddelse. Hvilket arkitekturen ellers (desværre) lægger op til.

Desuden, hvordan finder jeg over hovedet ud af hvad jeg skal teste på?

Jeg vil jo gerne vide hvad Login.login() retunerer.
Gravatar #4 - arne_v
30. mar. 2009 15:42
#3

Du burde kunne hente både status og fejl besked fra din bean.
Gravatar #5 - Windcape
30. mar. 2009 15:45
Men det kræver jeg gemmer status af Login.login() efter det er blevet kaldt?

Det virker meget underligt og ikke særlig elegant.

Altså

Login
- username: foo
- password: bar

JSF sætter Login.username og Login.password, og kalder login()

login() retunere fejl som response på et POST request.

Jeg kan så aflæse fejlværdien fra login() fra en eller anden magisk variabel der burde komme via viewstate.

Derudover undre jeg mig også meget over at jeg ikke kan mappe den til at kalde login(username,password) istedet for at skulle bruge setters før jeg kan kalde en parameterløs metode.

Jeg kan ikke rigtig se ideen i det.
Gravatar #6 - arne_v
30. mar. 2009 16:52
#5

Hvis du vil teste på det samme to gange så er du nødt til at teste to gange. Men det må kunne laves så selve username/password valideringen kun laves en gang.

Dine form felter er mappet til bean fields, så de data vil altid være der.
Gravatar #7 - arne_v
30. mar. 2009 17:03
Jeg prøver lige at bixe et eksempel.
Gravatar #8 - arne_v
30. mar. 2009 17:03
PS: username == "test" er skidt test. :-)
Gravatar #9 - Windcape
30. mar. 2009 17:08
arne_v (8) skrev:
PS: username == "test" er skidt test. :-)
Nogen teknisk grund, eller bare fordi at det er simpelt ? :)

Et eksempel ville være super, da jeg kun kan finde eksempler og dokumentation på hvordan man laver det med redirects til *andre* sider ved success/failure.
Gravatar #10 - arne_v
30. mar. 2009 17:30
#9

username == "test"

tester om det er samme objekt

username.equals("test")

tester om det er samme indhol

Stor forskel !
Gravatar #11 - arne_v
30. mar. 2009 17:32
Jeg er langtfra nogen guru til JSF, men følgende ser ud til at virke:

<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<!doctype html public "-//w3c/dtd HTML 4.01 Transitional//en">
<html>
<head>
<title>Login</title>
</head>
<body>
<c:if test='${login.res == "Failure"}'>
<c:out value='${login.msg}'/>
</c:if>
<f:view>
<h:form>
Username: <h:inputText id="un" value="#{login.un}"/>
<br>
Password: <h:inputSecret id="pw" value="#{login.pw}"/>
<br>
<h:commandButton action="#{login.login}" value="Login"/>
</h:form>
</f:view>
</html>


package test;

import java.io.Serializable;

public class Login implements Serializable {
private String un = "";
private String pw = "";
private String res = "";
private String msg = "";
public String getUn() {
return un;
}
public void setUn(String un) {
this.un = un;
}
public String getPw() {
return pw;
}
public void setPw(String pw) {
this.pw = pw;
}
public String getRes() {
return res;
}
public void setRes(String res) {
this.res = res;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String login() {
if(un.equals("test") && pw.equals("test")) {
res = "Success";
msg = "";
} else {
res = "Failure";
msg = "If you are a hacker then fuckoff !";
}
return res;
}
}


<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>Success</from-outcome>
<to-view-id>/something.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>Failure</from-outcome>
<to-view-id>/login.jsp</to-view-id>
</navigation-case>
</navigation-rule>
<managed-bean>
<managed-bean-name>login</managed-bean-name>
<managed-bean-class>test.Login</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>
Gravatar #12 - arne_v
30. mar. 2009 17:34
Det er hvad jeg har forstået at du gerne vil.

Ligesom JSF ikke er specielt velegnet til prototyping, så er det heller ikke specielt velegnet til hurtige eksempler på nettet.

Gravatar #13 - arne_v
30. mar. 2009 17:36
Jeg har spurgt om det før: hvilken JSF implementation bruger du ? Mojarra ? MyFaces ? Og nogle extra oveni ? Hvis du har hang til AJAX, så vil du sikkert kunne lide RichFaces.
Gravatar #14 - Windcape
30. mar. 2009 19:13
arne_v (13) skrev:
Jeg har spurgt om det før: hvilken JSF implementation bruger du ?
SUN Standard?

Så vidt jeg kan se er det Mojarra.

Takker for eksemplet, vil kigge på det her til aften.
Gravatar #15 - Windcape
30. mar. 2009 19:17
arne_v (10) skrev:
#9

username == "test"

tester om det er samme objekt
Ahh, doh. Glemte at rette det til på username.

Er blevet lidt for vandt til C#.
Gravatar #16 - Windcape
30. mar. 2009 21:01
Fik eksemplet til at virke, takker :)

Så skal jeg bare hitte ud af hvordan data persistance fungerer.
Fordi de er sikkert ikke glade for sessions.
Gravatar #17 - arne_v
30. mar. 2009 21:27
Jeg vil da tro at du skal bruge både sessions og persistering - bare til forskellige ting.

Der er rigeligt at vælge imellem med hensyn til persistering, men hvis du er til ORM, så vil jeg mene at enten Hibernate eller JPA (og implementationen kunne såmænd godt være Hibernate) var oplagte.
Gravatar #18 - arne_v
30. mar. 2009 21:28
Med hensyn til login - er det med vilje at du vil lade din applikation checke login og ikke lade containeren checke login ?
Gravatar #19 - Windcape
30. mar. 2009 21:57
arne_v (18) skrev:
Med hensyn til login - er det med vilje at du vil lade din applikation checke login og ikke lade containeren checke login ?
Er er overhovedet nogen som benytter HTTP authentication i dag?

Alt login fungerer da ligesom newz.dk, hvor man så slår op i en database ved login for at validere brugernavn og password.

Men det lader til at værre en størrere hovedpine at implementere login-check på givne undersider. Set i forhold til at jeg allerede benytter et framework på et framework, så burde det jo være lavet til at kunne klares med 2-3 linjers kode (ala. PHP).
Gravatar #20 - arne_v
30. mar. 2009 22:00
#19

Der er sikkert ikke mange som benytter HTTP auth idag.

Men din container understøtter også form baseret auth.
Gravatar #21 - arne_v
30. mar. 2009 22:02
#20

Du benytter 4 lag teknologier JSP/JSP/Servlet/Java. Container managed auth er i servlet.
Gravatar #22 - arne_v
30. mar. 2009 22:03
#20

Hvis du vil lave app managed auth (det sker at folk foretrækker det !), så skal du nok benytte dig af et filter.
Gravatar #23 - Windcape
31. mar. 2009 11:09
Men container auth, som f.eks. dette her til tomcat: http://www.onjava.com/pub/a/onjava/2002/06/12/form...

Der hardcoder man jo rettighederne, og kræver en restart af sin webserver for at ændre hvem der har adgang til hvad.

Normalt ville man udvikle en løsning så at man kan definere hvilket resources som role X kan tilgå via. databasen, så man aldrig skal opdatere sin kode eller restarte serveren.

Java EE ser ud til at have trang til at gøre det anderledes end hvad der er standard for alle andre web-teknologier.
Gravatar #24 - arne_v
31. mar. 2009 13:28
#23

Ja - det er den korte version af container managed auth.

Og jeg forstår ikke helt din kritik.

I web.xml angiver du hvilke roller der har adgang til hvilke resourcer.

Via et eller andet tool styrer du hvilke brugere der har hvilke roller.

Saa du beskytter admin delen af din app så kun brugere med administrator rolle kan tilgå den. Og giver bruger windcape den rolle.

Jeg har lidt svært ved at se behovet for dynamisk at have mulighed for at fjerne administrator rollens adgang til admin delen og i.s.f. give den til guest rollen.

Rollerne har normalt tilknyttet semantik.

Og jeg mener heller ikke at det er anderledes end i ASP.NET, så vidt jeg kan se så er det:

Java EE
--------

web.xml fragment:

<security-constraint>
<web-resource-collection>
<web-resource-name>some name</web-resource-name>
<url-pattern>/somepath</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>somerole</role-name>
</auth-constraint>
</security-constraint>

ASP.NET
---------

web.config fragment:

<location path="somepath">
<system.web>
<authorization>
<allow roles="somerole"/>
<deny users="*"/>
</authorization>
</system.web>
</location>

Præcis det samme.
Gravatar #25 - Windcape
31. mar. 2009 13:45
Men begge metoder kræver genstart (og evt. rekompilering) for at tilføje / fjerne roller, og tilføje / fjerne paths de kan tilgå.

Derudover låser man sig jo til een specific container hvis man laver den slags auth.

F.eks. i ASP.NET har jeg set apps som brugte IIS definerede users, altså næsten byggede det sammen med styresystemet.

Så ryger hele argumentet for at det ikke er låst til et miljø, hvilket jo er en af de eneste grunde til at vælge java i dag, som jeg ser det.

Men må nok indrømme at teknologien ihvertfald ikke gør det nemt at lave login anderledes. Men selv på den måde synes jeg det er ydest forvirrende at tildele roller til brugere, uden at skulle til at sætte databasen direkte i forbindelse med containeren (tomcat).

Det minder om en gammeldags fjollet måde at lave http auth på, der "bare" supportere forms nu, fordi at det er mere brugervenligt.
Gravatar #26 - Windcape
31. mar. 2009 14:22
Løsningen med et filter


public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException
{
HttpSession session = ((HttpServletRequest)request).getSession();
Authentication auth = (Authentication)session.getAttribute(Authentication.class.getSimpleName());

if((auth == null) || (!auth.isAuthenticated()))
{
((HttpServletResponse)response).sendRedirect("../login.jsp");
}
else
{
chain.doFilter(request, response);
}
}
Gravatar #27 - Windcape
31. mar. 2009 14:29
SUN er vel entligt lidt fjollede.

session.getAttribute(string arg0);

Istedet for at benytte string, skulle de da have benyttet et strong-typed classname for ordenlig refractoring.

Da vi diskutterede det i den anden tråd var netop refractoring en af de vigtige punkter.

Jeg valgte så at benytte MyClass.class.getSimpleName() men det virker bare fjollet set i forhold til f.eks. C#: typeof(MyClass).

(Og hvis jeg ser rigtigt bruges der slet ikke generics i JSF overhovedet?)
Gravatar #28 - arne_v
31. mar. 2009 14:36
#25

Det er korrekt at en ændring kræver en reload af app. Sådan er det bare.

Man låser sig ikke til en bestemt container. Syntaxen i web.xml (og kaldene i servlet API hvis man vil mixe den declarative security med lidt programmatic security) er helt standard, så din web app vil virke uændret i alle containere.

Opsætningen af bruger og rolle "databasen" er container specifik.

Tomcat kommer f.eks. med backends til:
- database via JDBC
- database via DataSource via JNDI
- LDAP via JNDI
- XML file
- JAAS
og derudover er det ret nemt at lave sin egen backend (nem som i 50 linier kode).


JBoss understøtter:
- properties file
- LDAP
- database
- certifikater
og det er også relativt nemt at lave sin egen.
Gravatar #29 - arne_v
31. mar. 2009 14:42
#27

Alle standard objekterne: application, session, request etc. bruger String som key.

Det er en restriktion. Men man undgår problemer med mutable keys på den måde.
Gravatar #30 - Windcape
31. mar. 2009 14:49
arne_v (29) skrev:
Men man undgår problemer med mutable keys på den måde.
Af ren og skær interesse, hvordan kan man lave en muteable key, hvis keys er en stærk type (MyClass.class i java).

Så vidt jeg kan se kan den kun ændre sig hvis man ændre navnet på klassen, hvilket jo alligevel vil kræve at man ændre sin string key.

Eller er det for at undgå 2 klasser med samme navn, i forskellige packages? Så at hvis man havde brugt en forkert (autoimports og typo's sker jo), så ville ens refractoring opdatere navnet forkert?

Eller noget helt tredje? :)
Gravatar #31 - arne_v
31. mar. 2009 15:03
#30

.setAttribute(Object key, Object value) og .getAttribute(Object key) vil tillade brug af mutable keys, hvilket kan gå grueligt galt.

.setAttribute(Class key, Object value) og .getAttribute(Class key) vil ikke tillade mutable keys, men jeg kan ikke rigtigt se nogen pointe i at bruge klasser som identifikation.
Gravatar #32 - Windcape
31. mar. 2009 15:12
arne_v (31) skrev:
men jeg kan ikke rigtigt se nogen pointe i at bruge klasser som identifikation.
Refractoring ville umidbart være en god grund synes jeg.
Gravatar #33 - arne_v
31. mar. 2009 15:20
#32

Kan du forklare det lidt nærmere. Jeg er slet ikke med.
Gravatar #34 - Windcape
31. mar. 2009 15:56
Hvis jeg skal ændre et klassenavn. Så ville refractoring jo ikke opdatere værdien af en string, hvor imod hvis jeg benytter ClassName så ville det jo blive opdateret.
Gravatar #35 - arne_v
31. mar. 2009 16:16
#34

Jeg er helt enig i at session.getAttribute(MyClass.class.getName()) er bedre end session.getAtttribute("mypackage.MyClass"), men hvorfor bruge klassenavnet ?

session.getAttribute("foobar") eller session.getAttribute(SesionKeys.FOOBAR) synes at give lidt mere fleksibilitet med hensyn til valg af et beskrivende navn fremfor at lægge sigt fast på et klasse navn.
Gravatar #36 - Windcape
31. mar. 2009 16:25
Oh, det var nok fordi at i dette her tilfælde tænkte jeg udelukkende på Managed Beans. Havde glemt at det også kunne benyttes til alm. sessions


<managed-bean>
<managed-bean-name>Authentication</managed-bean-name>
<managed-bean-class>website.Authentication</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
Gravatar #37 - arne_v
31. mar. 2009 17:04
#36

Det er også bare et navn. Du kan sagtens bruge et generisk navn uafhængigt af klassen.
Gå til top

Opret dig som bruger i dag

Det er gratis, og du binder dig ikke til noget.

Når du er oprettet som bruger, får du adgang til en lang række af sidens andre muligheder, såsom at udforme siden efter eget ønske og deltage i diskussionerne.

Opret Bruger Login