Přejít k navigační liště

Zdroják » Různé » Java na webovém serveru: posílání e-mailů a CAPTCHA

Java na webovém serveru: posílání e-mailů a CAPTCHA

Články Různé

Dnes se opět budeme věnovat praktickým ukázkám a naší aplikaci. Po předchozím díle o vlastních JSP značkách a servletech se dnes podíváme na to, jak z Javy na serveru posílat e-maily a jak chránit aplikaci proti spamu pomocí tzv. CAPTCHA.

Zatímco pro práci s e-maily si vystačíme se standardními prostředky platformy Java, pro implementaci anti-spamové ochrany budeme muse použít cizí knihovnu. Znalosti servletů probírané minule se nám budou hodit, protože CAPTCHA, kterou dnes použijeme, je založená právě na servletu.

Jen pro připomenutí: jako obvykle si stáhneme aktuální zdrojové kódy z Mercurialu (případně jako bzip2 archiv přes web):

$ hg pull
$ hg up "10. díl"

Posílání e-mailů

Ve webových aplikacích potřebujeme poměrně často posílat e-maily – ať už se jedná o zapomenutá hesla, upozornění správcům nebo třeba registrační e-maily. Vytvoříme si proto univerzální EJB komponentu, která zapouzdří odesílání zpráv, a následně upravíme proces registrace uživatele tak, aby zároveň odeslal upozornění uživateli, že byl zaregistrován.

Pro práci s elektronickou poštou budeme používat standardní JavaMail API. Nakonfigurujeme si proto v aplikačním serveru tzv. JavaMail Session a přiřadíme jí JNDI jméno.

Java 9

Důležité je vyplnit adresu SMTP serveru (v našem případě localhost) a výchozí adresu odesílatele.

V EJB komponentě získáme instanci JavaMail Session jednoduše pomocí anotace @Resource  – nemusíme tak konfigurovat javax.mail.Session ručně v naší aplikaci.

@Resource(lookup = "mail/nekurak.net")
Session smtpRelace;

Pokud bychom např. chtěli odesílat přes jiný SMTP server, stačí překonfigurovat nastavení mail/nekurak.net na úrovni aplikačního serveru a není potřeba zasahovat do aplikace (nasazovaný .war nebo .ear je pořád stejný).

Obecná metoda pro odeslání e-mailu se nachází ve třídě Postak a vypadá následovně:

public void odesliZpravu(Adresa komu, Adresa od, String predmet, String text) throws NekurakVyjimka {

    try {
        MimeMessage mimeZprava = new MimeMessage(smtpRelace);

        mimeZprava.addRecipient(RecipientType.TO, komu.getInternetAddress());
        if (od != null) {
        mimeZprava.setFrom(od.getInternetAddress());
        }
        mimeZprava.setSubject(predmet);
        mimeZprava.setText(text, "UTF-8");

        Transport.send(mimeZprava);
        log.info("Zpráva pro " + komu + " byla odeslána.");
    } catch (Exception e) {
        throw new NekurakVyjimka("Selhalo odesílání e-mailu pro: " + komu, e);
    }
}

Tuto metodu používáme ve třídě UzivatelEJB k odeslání e-mailu uživateli, který se právě zaregistroval (jen tehdy, pokud vyplní e-mail). Zprávy se posílají lokalizované do jazyka, který měl uživatel nastavený ve chvíli vyplňování registračního formuláře.

JMS – ano či ne?

V diskusi pod jedním z předchozích dílů se objevila myšlenka použít pro odesílání e-mailů JMS (Java Message Service). V praxi by to znamenalo, že by naše aplikace nepoužívala klasické JavaMail rozhraní, ale předala by e-mailovou zprávu v podobě objektu do nějaké JMS fronty a zpráva by se „někde jinde“ z JMS převzala a předala SMTP serveru.

Na otázku zda tento přístup uplatnit neexistuje jednoznačná odpověď. Argumentem pro JMS je to, že přijetí e-mailu SMTP serverem může nějakou dobu trvat, což by zdržovalo načtení stránky – takže by mohlo být vhodné použít JMS frontu a předání SMTP serveru provést asynchronně. Na druhou stranu SMTP server na lokálním stroji přijme zprávu prakticky okamžitě a zapojovat do procesu JMS znamená další článek řetězce, který se může pokazit. Záleží tedy hlavně na tom, jakou infrastrukturu máme vybudovanou – pokud nám na serveru běží SMTP server, nemá moc smysl zapojovat JMS. A obráceně: pokud máme fungující JMS systém, nemusíme na server instalovat SMTP server a můžeme použít JMS pro předání zpráv na jiný server, kde SMTP už máme (jenže i v tom případě stačí překonfigurovat JavaMail Session v aplikačním serveru, aby se nespojovala s localhostem, ale s jiným strojem).

Použít JMS pro prosté obalení e-mailů, jako transportní protokol místo SMTP bude ve většině případů nadbytečné. Smysluplnější je nepoužívat JMS pro nízkoúrovňové (technické) události typu „pošli e-mail“, ale pro události obchodního (byznys) charakteru typu „založ uživatele“.

Akademie Root vás zve na školení Základy programovacího jazyka Java a na následné školení Pokročilejší kurz jazyka Java.

CAPTCHA – ochrana proti spamu

Internet je bohužel plný spamerů, a tak je potřeba většinu formulářů chránit proti jejich nevyžádaným reklamám – obvykle pomocí tzv. CAPTCHA (completely automated public Turing test to tell computers and humans apart), což je test, který nám umožní rozlišit legitimní uživatele (živé lidi) od spamovacích robotů.

Kaptcha

Technologie JSP jako taková nemá vestavěnou podporu pro CAPTCHu, je tedy potřeba použít nějakou dodatečnou knihovnu. Jednou z implementací CAPTCHy v Javě je Kaptcha. Na jejím příkladě si ukážeme začlenění proti-spamové ochrany do webové aplikace.

Tato knihovna je otevřený software licencovaný pod Apache 2.0 licencí. Stáhneme si ji z jejích stránek hostovaných na Google Code a přidáme si příslušný .jar do webového modulu aplikace nekurak.net-web.

Kaptcha není jen knihovna, která umí generovat obrázky s různě pokřiveným textem – poskytuje nám i servlet, který slouží ke zpřístupnění těchto obrázků na webu. Tento servlet si aktivujeme přidáním následujících řádků do souboru web.xml:

<servlet>
    <servlet-name>Kaptcha</servlet-name>
    <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>Kaptcha</servlet-name>
    <url-pattern>/kaptcha.jpg</url-pattern>
</servlet-mapping>

Do HTML formuláře, který chceme ochránit, pak přidáme CAPTCHu pomocí tohoto kódu:

<img src="kaptcha.jpg" alt="ochrana proti spamu"/>
<label>Opište kód: <input type="text" name="kaptcha" maxlength="255"/></label>

Java 9

Jednoduše vložíme obrázek a políčko formuláře. Servlet při generování obrázku nastavuje atribut relace KAPTCHA_SESSION_KEY na hodnotu, kterou právě vygeneroval. Po odeslání formuláře si tak můžeme zkontrolovat, zda uživatel opsal správný text:

<c:choose>
    <c:when test="${sessionScope['KAPTCHA_SESSION_KEY'] == param.kaptcha}">
    <p class="informacniHlaska">Správně opsaný kód z obrázku.</p>
    </c:when>
    <c:otherwise>
    <p class="chybovaHlaska">Špatně obsaný kód z obrázku.</p>
    </c:otherwise>
</c:choose>

Jelikož se někdy může vygenerovat špatně čitelný text, je dobré dát uživateli možnost přegenerovat si obrázek aniž by musel obnovovat celou stránku. Toho lze dosáhnout tímto kouskem JavaScriptu:

<script type="text/javascript">
    $(function(){
        $('#kaptchaIMG').click(function () { $(this).attr('src', 'kaptcha.jpg?' + Math.floor(Math.random()*100) ); })
    });
</script>

Používáme zde JavaScriptovou knihovnu jQuery, kterou jsme si do naší aplikace přidali už v minulém díle kvůli prohlížečce obrázků. Uživateli tak stačí kliknout na nečitelný obrázek a vygeneruje se mu nový.

Celý příklad naleznete v souboru kaptcha.jsp a vyzkoušet si ho můžete na adrese http://nekurak­.net/kaptcha.jsp.

Knihovna Kaptcha generuje obrázky v poměrně dobré kvalitě (čitelné) a implementovat pomocí ní do svých stránek ochranu proti spamu je snadné. Bohužel ale trpí podobným neduhem jako CAPTCHA na mnoha webech. Pokud si totiž uživatel otevře více stejných formulářů najednou, v proměnné na serveru je uložena pouze hodnota naposledy vygenerovaného obrázku. Když se uživatel pokusí odeslat dříve otevřený formulář, server ho odmítne, přestože uživatel obrázek opsal správně. Řešením by bylo ukládat si všechny v poslední době vygenerované kódy nebo tyto kódy na straně serveru vůbec neukládat a využít místo toho hashování a tajný klíč známý jen serveru.

Závěr

V dnešním díle jsme se naučili posílat e-maily z EJB komponenty a ukázali jsme si, jak ochránit naši aplikaci proti spamu pomocí knihovny Kaptcha. Příště se budeme věnovat tvorbě REST API pro naši aplikaci.

Odkazy

Komentáře

Subscribe
Upozornit na
guest
4 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
xtonda

1. Z jakého důvodu je ta universální navržena jako EJB? Přijde mi, že to není nutné, my to máme prostě jako běžnou utility třídu.

2. Odesílání přes JMS používáme docela dlouho a dost se to osvědčilo, JMS běžící na stejném či sousedním serveru je daleko dostupnější a spolehlivější než SMTP rozhraní Exchange serveru kdesi, odesílající aplikace je rychle vyřízena a nemusí řešit nedostupnost SMTP serveru.

xtonda

Lookup session z JNDI je na pár řádků, doslova. Případně i na to se dá udělat nějaký helper, navíc jsme to navrhovali pro Javu 1.4.

U nás se občas stane, že některý z Exchange serverů je nedostupný, blbne konektivita, při přečíslování IP trvá než se občerství DNS, atp.

My máme to předání do JMS napsané jako vlastní JavaMail Transport, takže přepnutí „do JMS“/„přímo na SMTP“ by znamenalo jen změnit v konfiguraci JNDI jméno pro lookup JavaMail session. Takže když je potřeba napsat kód odesílající mail, můžeme použít vlastní zjednodušující API pro vytvoření a odeslání mailu nebo, pokud potřebuji něco speciálního, můžeme použít přímo JavaMail, jedinou podmínkou je načíst JNDI jméno JavaMail session z konfigurace a použít tuto session.

Jak koukám na to vaše zapouzdření, tak možnost odeslání plaintext mailu jednomu příjemci je poněkud omezující.

exception

dobry den

natyka sa to priamo temy, ale aky ma podla vas vyznam pouzyvat genericke catched vynimky?
za hlavne nevyhody ich pouzytia povazujem najma to ze
1. nesuvisia priamo s biznis logikou metody, napriek tomu su castou jej signatury a kazia rozhranie
2. vynimka sa prenasa do dalsej hierarchie volania
stretnut sa s api kde kazda metoda deklaruje SpecificApiEx­ception nie je ojedinele
aky to ma potom vyznam?

otazka nebola smerovana ofenzivne, netyka sa priamo temy ale mna osobne viac ako funkcionalita na takychto clankoch zaujima sposob kodovania, ktory je casto podcenovany a pritom napr v pripade error handlingu sa nejedna vobec o trivialnu zalezitost, skor naopak

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.