Napojení na SQL databázi.
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/analýza/jaas.txt Tue Feb 07 00:27:39 2012 +0100
1.3 @@ -0,0 +1,26 @@
1.4 +Java – Glassfish – JAAS
1.5 +-----------------------
1.6 +
1.7 +Důležité pojmy:
1.8 + - Realm – vytváří se jen jednou, obsahuje parametry (Properties)
1.9 + - Login modul – vytváří se při každém pokusu o přihlášení
1.10 +
1.11 + ----------------------------------
1.12 + Uživatel ← → Login modul ← → Realm
1.13 + ----------------------------------
1.14 +
1.15 +V konfiguraci Glassfishe definujeme ověřování uživatelů jako Realm, ke kterému zadáme JAAS Context.
1.16 +Nemůžeme tam zadat cokoli, musí to být hodnota z login.conf – podle ní se totiž dohledá příslušný Login modul.
1.17 +
1.18 +Viz konfigurace/glassfish/login.conf
1.19 +
1.20 +Knihovnu sql-java-prihlasovani.jar musíme dát do /opt/glassfish3/glassfish/lib/
1.21 +ne do /opt/glassfish3/glassfish/domains/domain1/lib/ext/
1.22 +Jinak bychom totiž dostali tuto chybu:
1.23 +
1.24 +--------------------------------------------------------------------------------
1.25 +[#|2012-02-06T20:04:34.502+0100|SEVERE|glassfish3.1.1|javax.enterprise.system.tools.admin.com.sun.enterprise.v3.admin|_ThreadID=109;_ThreadName=Thread-2;|Exception in command execu
1.26 +tion : java.lang.NoClassDefFoundError: com/sun/appserv/security/AppservRealm
1.27 +java.lang.NoClassDefFoundError: com/sun/appserv/security/AppservRealm
1.28 +…
1.29 +--------------------------------------------------------------------------------
2.1 --- a/java/sql-java-prihlasovani/nbproject/build-impl.xml Thu Jul 21 23:41:05 2011 +0200
2.2 +++ b/java/sql-java-prihlasovani/nbproject/build-impl.xml Tue Feb 07 00:27:39 2012 +0100
2.3 @@ -20,10 +20,10 @@
2.4
2.5 -->
2.6 <project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="sql-java-prihlasovani-impl">
2.7 - <fail message="Please build using Ant 1.7.1 or higher.">
2.8 + <fail message="Please build using Ant 1.8.0 or higher.">
2.9 <condition>
2.10 <not>
2.11 - <antversion atleast="1.7.1"/>
2.12 + <antversion atleast="1.8.0"/>
2.13 </not>
2.14 </condition>
2.15 </fail>
2.16 @@ -198,6 +198,7 @@
2.17 <property name="javac.fork" value="${jdkBug6558476}"/>
2.18 <property name="jar.index" value="false"/>
2.19 <property name="jar.index.metainf" value="${jar.index}"/>
2.20 + <property name="copylibs.rebase" value="true"/>
2.21 <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
2.22 </target>
2.23 <target name="-post-init">
2.24 @@ -384,6 +385,7 @@
2.25 <property environment="env"/>
2.26 <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
2.27 <java classname="@{classname}" dir="${profiler.info.dir}" fork="true" jvm="${profiler.info.jvm}">
2.28 + <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
2.29 <jvmarg value="${profiler.info.jvmargs.agent}"/>
2.30 <jvmarg line="${profiler.info.jvmargs}"/>
2.31 <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
2.32 @@ -514,7 +516,7 @@
2.33 </chainedmapper>
2.34 </pathconvert>
2.35 <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
2.36 - <copylibs compress="${jar.compress}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
2.37 + <copylibs compress="${jar.compress}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" rebase="${copylibs.rebase}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
2.38 <fileset dir="${build.classes.dir}"/>
2.39 <manifest>
2.40 <attribute name="Class-Path" value="${jar.classpath}"/>
2.41 @@ -839,16 +841,26 @@
2.42 -->
2.43 <target depends="init" if="have.sources" name="-javadoc-build">
2.44 <mkdir dir="${dist.javadoc.dir}"/>
2.45 + <condition else="" property="javadoc.endorsed.classpath.cmd.line.arg" value="-J${endorsed.classpath.cmd.line.arg}">
2.46 + <and>
2.47 + <isset property="endorsed.classpath.cmd.line.arg"/>
2.48 + <not>
2.49 + <equals arg1="${endorsed.classpath.cmd.line.arg}" arg2=""/>
2.50 + </not>
2.51 + </and>
2.52 + </condition>
2.53 <javadoc additionalparam="${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
2.54 <classpath>
2.55 <path path="${javac.classpath}"/>
2.56 </classpath>
2.57 - <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
2.58 + <fileset dir="${src.dir}" excludes="*.java,${excludes}" includes="${includes}">
2.59 <filename name="**/*.java"/>
2.60 </fileset>
2.61 <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
2.62 <include name="**/*.java"/>
2.63 + <exclude name="*.java"/>
2.64 </fileset>
2.65 + <arg line="${javadoc.endorsed.classpath.cmd.line.arg}"/>
2.66 </javadoc>
2.67 <copy todir="${dist.javadoc.dir}">
2.68 <fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
3.1 --- a/java/sql-java-prihlasovani/nbproject/genfiles.properties Thu Jul 21 23:41:05 2011 +0200
3.2 +++ b/java/sql-java-prihlasovani/nbproject/genfiles.properties Tue Feb 07 00:27:39 2012 +0100
3.3 @@ -4,5 +4,5 @@
3.4 # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
3.5 # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
3.6 nbproject/build-impl.xml.data.CRC32=916fc21a
3.7 -nbproject/build-impl.xml.script.CRC32=7e41f0b2
3.8 -nbproject/build-impl.xml.stylesheet.CRC32=0c01fd8e@1.43.1.45
3.9 +nbproject/build-impl.xml.script.CRC32=2d24efe9
3.10 +nbproject/build-impl.xml.stylesheet.CRC32=fcddb364@1.50.1.46
4.1 --- a/java/sql-java-prihlasovani/nbproject/project.properties Thu Jul 21 23:41:05 2011 +0200
4.2 +++ b/java/sql-java-prihlasovani/nbproject/project.properties Tue Feb 07 00:27:39 2012 +0100
4.3 @@ -1,9 +1,10 @@
4.4 annotation.processing.enabled=true
4.5 annotation.processing.enabled.in.editor=false
4.6 -annotation.processing.processor.options=
4.7 annotation.processing.processors.list=
4.8 annotation.processing.run.all.processors=true
4.9 annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
4.10 +application.title=sql-java-prihlasovani
4.11 +application.vendor=fiki
4.12 build.classes.dir=${build.dir}/classes
4.13 build.classes.excludes=**/*.java,**/*.form
4.14 # This directory is removed when the project is cleaned:
4.15 @@ -24,6 +25,7 @@
4.16 dist.dir=dist
4.17 dist.jar=${dist.dir}/sql-java-prihlasovani.jar
4.18 dist.javadoc.dir=${dist.dir}/javadoc
4.19 +endorsed.classpath=
4.20 excludes=
4.21 includes=**
4.22 jar.compress=false
4.23 @@ -34,8 +36,8 @@
4.24 javac.deprecation=false
4.25 javac.processorpath=\
4.26 ${javac.classpath}
4.27 -javac.source=1.6
4.28 -javac.target=1.6
4.29 +javac.source=1.7
4.30 +javac.target=1.7
4.31 javac.test.classpath=\
4.32 ${javac.classpath}:\
4.33 ${build.classes.dir}:\
5.1 --- a/java/sql-java-prihlasovani/src/cz/frantovo/jaas/sql/SQLLoginModul.java Thu Jul 21 23:41:05 2011 +0200
5.2 +++ b/java/sql-java-prihlasovani/src/cz/frantovo/jaas/sql/SQLLoginModul.java Tue Feb 07 00:27:39 2012 +0100
5.3 @@ -1,33 +1,58 @@
5.4 package cz.frantovo.jaas.sql;
5.5
5.6 import com.sun.appserv.security.AppservPasswordLoginModule;
5.7 +import com.sun.enterprise.security.auth.realm.NoSuchUserException;
5.8 +import java.util.ArrayList;
5.9 import java.util.Arrays;
5.10 +import java.util.Enumeration;
5.11 +import java.util.List;
5.12 import java.util.logging.Level;
5.13 import java.util.logging.Logger;
5.14 import javax.security.auth.login.LoginException;
5.15
5.16 /**
5.17 * Přihlašovací modul pro SQL doménu.
5.18 - * TODO: později bude potomkem <code>com.sun.appserv.security.AbstractLoginModule</code>
5.19 + *
5.20 + * TODO: později bude potomkem
5.21 + * <code>com.sun.appserv.security.AbstractLoginModule</code>
5.22 + *
5.23 * @author fiki
5.24 */
5.25 public class SQLLoginModul extends AppservPasswordLoginModule {
5.26
5.27 - /** viz konfigurace v login.conf */
5.28 + /**
5.29 + * viz konfigurace v login.conf
5.30 + */
5.31 public static final String VÝCHOZÍ_JAAS_KONTEXT = "sqlRealm";
5.32 private static final Logger log = Logger.getLogger(SQLLoginModul.class.getName());
5.33
5.34 @Override
5.35 protected void authenticateUser() throws LoginException {
5.36 -
5.37 - if (_currentRealm instanceof SQLRealm) {
5.38 -
5.39 - SQLRealm sqlRealm = (SQLRealm)_currentRealm;
5.40 - String skupiny[] = sqlRealm.ověřUživatele(_username, _passwd);
5.41 - commitUserAuthentication(skupiny);
5.42 -
5.43 + if (_currentRealm instanceof SQLRealm) {
5.44 + try {
5.45 + SQLRealm sqlRealm = (SQLRealm) _currentRealm;
5.46 + sqlRealm.ověřUživatele(_username, _passwd);
5.47 +
5.48 + String skupiny[] = poleSkupin(sqlRealm.getGroupNames(_username));
5.49 + log.log(Level.INFO, "Uživatel {0} byl úspěšně ověřen a je členem těchto skupin: {1}", new Object[]{_username, Arrays.toString(skupiny)});
5.50 + commitUserAuthentication(skupiny);
5.51 + } catch (NoSuchUserException nue) {
5.52 + log.log(Level.SEVERE, "Uživatele se podařilo ověřit, ale při pokusu o zjištění jeho skupin došlo k chybě. Uživatel: " + _username, nue);
5.53 + throw new LoginException("Neexistující uživatel: " + _username);
5.54 + }
5.55 } else {
5.56 throw new LoginException("Špatný realm: " + _currentRealm + " Očekávám: SQLRealm.");
5.57 }
5.58 }
5.59 +
5.60 + /**
5.61 + * Převede výčet na pole
5.62 + */
5.63 + private static String[] poleSkupin(Enumeration<String> e) {
5.64 + List<String> l = new ArrayList<>();
5.65 + while (e.hasMoreElements()) {
5.66 + l.add(e.nextElement());
5.67 + }
5.68 + return l.toArray(new String[0]);
5.69 + }
5.70 }
6.1 --- a/java/sql-java-prihlasovani/src/cz/frantovo/jaas/sql/SQLRealm.java Thu Jul 21 23:41:05 2011 +0200
6.2 +++ b/java/sql-java-prihlasovani/src/cz/frantovo/jaas/sql/SQLRealm.java Tue Feb 07 00:27:39 2012 +0100
6.3 @@ -4,22 +4,46 @@
6.4 import com.sun.enterprise.security.auth.realm.BadRealmException;
6.5 import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
6.6 import com.sun.enterprise.security.auth.realm.NoSuchUserException;
6.7 +import java.sql.Connection;
6.8 +import java.sql.PreparedStatement;
6.9 +import java.sql.ResultSet;
6.10 +import java.sql.SQLException;
6.11 +import java.sql.Statement;
6.12 +import java.util.ArrayList;
6.13 +import java.util.Collection;
6.14 +import java.util.Collections;
6.15 import java.util.Enumeration;
6.16 import java.util.Properties;
6.17 import java.util.logging.Level;
6.18 -import java.util.logging.Logger;
6.19 +import javax.naming.InitialContext;
6.20 +import javax.naming.NamingException;
6.21 import javax.security.auth.login.LoginException;
6.22 +import javax.sql.DataSource;
6.23
6.24 /**
6.25 - * Bezpečnostní doména.
6.26 - * Uživatelé jsou uloženi v SQL databázi.
6.27 + * Bezpečnostní doména. Uživatelé jsou uloženi v SQL databázi.
6.28 + *
6.29 * @author fiki
6.30 */
6.31 public class SQLRealm extends AppservRealm {
6.32
6.33 private static final String AUTH_TYPE = "Ověřuje uživatele proti SQL databázi.";
6.34 - private static final Logger log = Logger.getLogger(SQLRealm.class.getName());
6.35 + private static final String PARAM_JNDI = "jndi";
6.36 + private static final String PARAM_SQL_HESLO = "sql_heslo";
6.37 + private static final String PARAM_SQL_SKUPINY_UŽIVATELE = "sql_skupiny_uzivatele";
6.38 + private static final String PARAM_SQL_SKUPINY_VŠECHNY = "sql_skupiny_vsechny";
6.39 + private DataSource datovýZdroj;
6.40 + private String sqlHeslo;
6.41 + private String sqlSkupinyUživatele;
6.42 + private String sqlSkupinyVšechny;
6.43
6.44 + /**
6.45 + * Načteme a zkontrolujeme parametry
6.46 + *
6.47 + * @param parametry
6.48 + * @throws BadRealmException pokud je v parametrech chyba
6.49 + * @throws NoSuchRealmException
6.50 + */
6.51 @Override
6.52 public void init(Properties parametry) throws BadRealmException, NoSuchRealmException {
6.53 super.init(parametry);
6.54 @@ -27,9 +51,26 @@
6.55 String jaasContext = parametry.getProperty(JAAS_CONTEXT_PARAM, SQLLoginModul.VÝCHOZÍ_JAAS_KONTEXT);
6.56 setProperty(JAAS_CONTEXT_PARAM, jaasContext);
6.57
6.58 - log.log(Level.INFO, "SQLRealm úspěšně vytvořen. JaasContext: {0}", jaasContext);
6.59 + /** Databázové spojení */
6.60 + {
6.61 + String jndi = najdiParametr(parametry, PARAM_JNDI, "název datového zdroje");
6.62 + try {
6.63 + InitialContext k = new InitialContext();
6.64 + jndi = parametry.getProperty(PARAM_JNDI);
6.65 + datovýZdroj = (DataSource) k.lookup(jndi);
6.66 + } catch (NamingException e) {
6.67 + throw new BadRealmException("Datový zdroj s tímto názvem nebyl nalezen: " + jndi, e);
6.68 + }
6.69 + }
6.70 +
6.71 + /** SQL dotazy */
6.72 + sqlHeslo = najdiParametr(parametry, PARAM_SQL_HESLO, "SQL dotaz pro ověření jména a hesla");
6.73 + sqlSkupinyUživatele = najdiParametr(parametry, PARAM_SQL_SKUPINY_UŽIVATELE, "SQL dotaz pro zjištění skupin uživatele");
6.74 + sqlSkupinyVšechny = najdiParametr(parametry, PARAM_SQL_SKUPINY_VŠECHNY, "SQL dotaz pro zjištění všech skupin");
6.75 +
6.76 + _logger.log(Level.INFO, "SQLRealm úspěšně vytvořen. JaasContext: {0}", jaasContext);
6.77 }
6.78 -
6.79 +
6.80 @Override
6.81 public String getAuthType() {
6.82 return AUTH_TYPE;
6.83 @@ -41,24 +82,107 @@
6.84 * @throws NoSuchUserException když uživatel s tímto jménem neexistuje.
6.85 */
6.86 @Override
6.87 - public Enumeration getGroupNames(String uživatel) throws NoSuchUserException {
6.88 - throw new NoSuchUserException("Metoda zatím není implementována. Uživatel: " + uživatel);
6.89 + public Enumeration<String> getGroupNames(String uživatel) throws NoSuchUserException {
6.90 + Collection<String> skupiny = new ArrayList<>();
6.91 +
6.92 + Connection s = null;
6.93 + PreparedStatement ps = null;
6.94 + ResultSet rs = null;
6.95 + try {
6.96 + s = datovýZdroj.getConnection();
6.97 + ps = s.prepareStatement(sqlSkupinyUživatele);
6.98 + ps.setString(1, uživatel);
6.99 + rs = ps.executeQuery();
6.100 +
6.101 + while (rs.next()) {
6.102 + skupiny.add(rs.getString(1));
6.103 + }
6.104 +
6.105 + return Collections.enumeration(skupiny);
6.106 +
6.107 + } catch (Exception e) {
6.108 + String hláška = "Chyba při zjišťování skupin uživatele: " + uživatel + ".";
6.109 + _logger.log(Level.WARNING, hláška, e);
6.110 + throw new NoSuchUserException(hláška);
6.111 + } finally {
6.112 + zavri(s, ps, rs);
6.113 + }
6.114 }
6.115
6.116 /**
6.117 *
6.118 * @param jméno uživatelské jméno
6.119 * @param heslo heslo
6.120 - * @return seznam skupin, do kterých uživatel patří
6.121 + * @return true pokud je jméno a heslo v pořádku | false nikdy – vyhazuje výjimku
6.122 + * @throws LoginException pokud je jméno nebo heslo neplatné nebo došlo k jiné chybě
6.123 */
6.124 - public String[] ověřUživatele(String jméno, char[] heslo) throws LoginException {
6.125 + public boolean ověřUživatele(String jméno, char[] heslo) throws LoginException {
6.126
6.127 - // TODO: skutečně ověřovat heslo proti databázi
6.128 + Connection s = null;
6.129 + PreparedStatement ps = null;
6.130 + ResultSet rs = null;
6.131 + try {
6.132 + s = datovýZdroj.getConnection();
6.133 + ps = s.prepareStatement(sqlHeslo);
6.134 + ps.setString(1, jméno);
6.135 + ps.setString(2, new String(heslo));
6.136 + rs = ps.executeQuery();
6.137
6.138 - if (heslo != null && heslo.length == 3) {
6.139 - return new String[]{"bezny"};
6.140 + if (rs.next()) {
6.141 + String dbJméno = rs.getString(1);
6.142 + if (dbJméno.equals(jméno)) {
6.143 + // OK – úspěšné ověření
6.144 + return true;
6.145 + } else {
6.146 + throw new LoginException("Nebyl nalezen správný uživatel: " + jméno + " != " + dbJméno);
6.147 + }
6.148 + } else {
6.149 + throw new LoginException("Uživatel nebyl nalezen: " + jméno);
6.150 + }
6.151 + } catch (SQLException e) {
6.152 + String hláška = "SQL chyba při ověřování hesla uživatele: " + jméno + ".";
6.153 + _logger.log(Level.WARNING, hláška, e);
6.154 + throw new LoginException(hláška);
6.155 + } finally {
6.156 + zavri(s, ps, rs);
6.157 + }
6.158 + }
6.159 +
6.160 + private String najdiParametr(Properties parametry, String názevParametru, String popis) throws BadRealmException {
6.161 + String hodnotaParametru = parametry.getProperty(názevParametru);
6.162 +
6.163 + if (hodnotaParametru == null || hodnotaParametru.length() < 1) {
6.164 + throw new BadRealmException("Chybí " + popis + " – parametr: " + názevParametru);
6.165 } else {
6.166 - throw new LoginException("Heslo musí mít tři znaky :-P");
6.167 + return hodnotaParametru;
6.168 + }
6.169 + }
6.170 +
6.171 + /**
6.172 + * Zavře všechno
6.173 + *
6.174 + * @param spojeni DB spojení
6.175 + * @param prikaz DB dotaz
6.176 + * @param vysledek DB výsledek
6.177 + */
6.178 + private static void zavri(Connection spojeni, Statement prikaz, ResultSet vysledek) {
6.179 + if (vysledek != null) {
6.180 + try {
6.181 + vysledek.close();
6.182 + } catch (Exception e) {
6.183 + }
6.184 + }
6.185 + if (prikaz != null) {
6.186 + try {
6.187 + prikaz.close();
6.188 + } catch (Exception e) {
6.189 + }
6.190 + }
6.191 + if (spojeni != null) {
6.192 + try {
6.193 + spojeni.close();
6.194 + } catch (Exception e) {
6.195 + }
6.196 }
6.197 }
6.198 }
7.1 --- a/java/sql-java-prihlasovani/test/cz/frantovo/jaas/sql/SQLRealmTest.java Thu Jul 21 23:41:05 2011 +0200
7.2 +++ b/java/sql-java-prihlasovani/test/cz/frantovo/jaas/sql/SQLRealmTest.java Tue Feb 07 00:27:39 2012 +0100
7.3 @@ -36,6 +36,7 @@
7.4 public void testGetAuthType() {
7.5 String authType = realm.getAuthType();
7.6 System.out.println("authType = " + authType);
7.7 - assertTrue("authType musí být nenulový a neprázdný", authType != null && authType.trim().length() > 0);
7.8 + assertTrue("authType musí být nenulový", authType != null);
7.9 + assertTrue("authType musí být neprázdný", authType.trim().length() > 0);
7.10 }
7.11 }