java/sql-java-prihlasovani/src/cz/frantovo/jaas/sql/SQLRealm.java
author František Kučera <franta-hg@frantovo.cz>
Tue, 07 Feb 2012 18:03:31 +0100
changeset 7 46bb283a674d
parent 6 aff44e80f418
permissions -rw-r--r--
uživatelé a skupiny v SQL databázi, parametrizace, hellDesk: #11
     1 package cz.frantovo.jaas.sql;
     2 
     3 import com.sun.enterprise.security.auth.realm.BadRealmException;
     4 import com.sun.enterprise.security.auth.realm.NoSuchUserException;
     5 import java.sql.Connection;
     6 import java.sql.PreparedStatement;
     7 import java.sql.ResultSet;
     8 import java.sql.SQLException;
     9 import java.sql.Statement;
    10 import java.util.ArrayList;
    11 import java.util.Collection;
    12 import java.util.Collections;
    13 import java.util.Enumeration;
    14 import java.util.logging.Level;
    15 import javax.naming.InitialContext;
    16 import javax.naming.NamingException;
    17 import javax.security.auth.login.LoginException;
    18 import javax.sql.DataSource;
    19 
    20 /**
    21  * Bezpečnostní doména. Uživatelé jsou uloženi v SQL databázi.
    22  *
    23  * @author fiki
    24  */
    25 public class SQLRealm extends ParametrizovanýRealm {
    26 
    27 	private static final String AUTH_TYPE = "Ověřuje uživatele proti SQL databázi.";
    28 	/**
    29 	 * <p>JNDI jméno datového zdroje – odkazuje na instanci {@linkplain javax.sql.DataSource}</p>
    30 	 *
    31 	 * <p>Příklady:</p>
    32 	 *
    33 	 * <ul>
    34 	 * <li><code>jdbc/mojeAplikace</code></li>
    35 	 * </ul>
    36 	 */
    37 	public static final String PARAM_JNDI = "jndi";
    38 	/** Pokud je nastaveno na "true", nebude se při inicializaci testovat spojení s databází */
    39 	public static final String PARAM_NETESTOVAT_SPOJENÍ = "netestovat_spojeni";
    40 	/**
    41 	 * <p>SQL dotaz pro kontrolu hesla.</p>
    42 	 *
    43 	 * <p>Má dva parametry:</p>
    44 	 * <ol>
    45 	 * <li>uživatelské jméno</li>
    46 	 * <li>heslo</li>
    47 	 * </ol>
    48 	 *
    49 	 * <p>Vrací:</p>
    50 	 * <ul>
    51 	 * <li>Při úspěšném ověření jednu hodnotu: uživatelské jméno</li>
    52 	 * <li>Při neexistujícím uživateli nebo špatném hesle: prázdný výsledek (nula řádků)</li>
    53 	 * <li>Při jiné chybě: vyhazuje výjimku</li>
    54 	 * </ul>
    55 	 *
    56 	 * <p>Příklady:</p>
    57 	 *
    58 	 * <ul>
    59 	 * <li><code>SELECT jmeno FROM uzivatel WHERE jmeno = ? AND heslo = ?</code></li>
    60 	 * <li><code>SELECT jmeno FROM uzivatel WHERE jmeno = ? AND heslo = sha1(?)</code></li>
    61 	 * <li><code>SELECT zkontroluj_heslo(?, ?)</code></li>
    62 	 * </ul>
    63 	 *
    64 	 * <p>(hashovací a jiné funkce musí být samozřejmě podporované databází)</p>
    65 	 */
    66 	public static final String PARAM_SQL_HESLO = "sql_heslo";
    67 	/**
    68 	 * <p>SQL dotaz pro zjištění skupin daného uživatele.</p>
    69 	 *
    70 	 * <p>Má jeden parametr: jméno uživatele</p>
    71 	 *
    72 	 * <p>Příklady:</p>
    73 	 *
    74 	 * <ul>
    75 	 * <li><code>SELECT nazev FROM skupina WHERE uzivatel = ?</code></li>
    76 	 * </ul>
    77 	 */
    78 	public static final String PARAM_SQL_SKUPINY_UŽIVATELE = "sql_skupiny_uzivatele";
    79 	/**
    80 	 * <p>SQL dotaz pro zjištění všech skupin v dané bezpečnostní doméně.</p>
    81 	 *
    82 	 * <p>Příklady:</p>
    83 	 *
    84 	 * <ul>
    85 	 * <li><code>SELECT nazev FROM skupina</code></li>
    86 	 * </ul>
    87 	 */
    88 	public static final String PARAM_SQL_SKUPINY_VŠECHNY = "sql_skupiny_vsechny";
    89 	private String jndiDatovéhoZdroje;
    90 	private String sqlHeslo;
    91 	private String sqlSkupinyUživatele;
    92 	private String sqlSkupinyVšechny;
    93 
    94 	@Override
    95 	protected void parametrizuj() throws BadRealmException {
    96 		String jaasContext = getProperties().getProperty(JAAS_CONTEXT_PARAM, SQLLoginModul.VÝCHOZÍ_JAAS_KONTEXT);
    97 		getProperties().setProperty(JAAS_CONTEXT_PARAM, jaasContext);
    98 
    99 		/** Databázové spojení */
   100 		jndiDatovéhoZdroje = najdiParametr(PARAM_JNDI, "název datového zdroje");
   101 		testSpojení();
   102 
   103 		/** SQL dotazy */
   104 		sqlHeslo = najdiParametr(PARAM_SQL_HESLO, "SQL dotaz pro ověření jména a hesla");
   105 		sqlSkupinyUživatele = najdiParametr(PARAM_SQL_SKUPINY_UŽIVATELE, "SQL dotaz pro zjištění skupin uživatele");
   106 		sqlSkupinyVšechny = najdiParametr(PARAM_SQL_SKUPINY_VŠECHNY, "SQL dotaz pro zjištění všech skupin");
   107 
   108 		_logger.log(Level.INFO, "SQLRealm úspěšně parametrizován. JaasContext: {0}, počet parametrů: {1}", new Object[]{getJAASContext(), getProperties().size()});
   109 	}
   110 
   111 	private void testSpojení() throws BadRealmException {
   112 		if (Boolean.valueOf(getProperty(PARAM_NETESTOVAT_SPOJENÍ))) {
   113 			_logger.log(Level.WARNING, "Netestujeme databázové spojení při inicializaci.");
   114 		} else {
   115 			try {
   116 				Connection s = getSpojení();
   117 				s.close();
   118 			} catch (NamingException e) {
   119 				throw new BadRealmException("Datový zdroj s tímto názvem nebyl nalezen: " + jndiDatovéhoZdroje, e);
   120 			} catch (SQLException e) {
   121 				throw new BadRealmException("Nepodařilo se navázat spojení s DB datového zdroje: " + jndiDatovéhoZdroje, e);
   122 			}
   123 		}
   124 	}
   125 
   126 	private Connection getSpojení() throws NamingException, SQLException {
   127 		InitialContext k = new InitialContext();
   128 		DataSource datovýZdroj = (DataSource) k.lookup(jndiDatovéhoZdroje);
   129 		return datovýZdroj.getConnection();
   130 	}
   131 
   132 	@Override
   133 	public String getAuthType() {
   134 		return AUTH_TYPE;
   135 	}
   136 
   137 	/**
   138 	 * @param uživatel přihlašovací jméno uživatele
   139 	 * @return seznam skupin, ve kterých se daný uživatel nachází
   140 	 * @throws NoSuchUserException když uživatel s tímto jménem neexistuje.
   141 	 */
   142 	@Override
   143 	public Enumeration<String> getGroupNames(String uživatel) throws NoSuchUserException {
   144 		try {
   145 			return getSkupiny(sqlSkupinyUživatele, uživatel);
   146 		} catch (NamingException | SQLException e) {
   147 			String hláška = "Chyba při zjišťování skupin uživatele: " + uživatel + ".";
   148 			_logger.log(Level.WARNING, hláška, e);
   149 			throw new NoSuchUserException(hláška);
   150 		}
   151 	}
   152 
   153 	/**
   154 	 * @return seznam všech skupin v této bezpečnostní doméně
   155 	 * @throws BadRealmException v případě SQL chyby
   156 	 */
   157 	@Override
   158 	public Enumeration getGroupNames() throws BadRealmException {
   159 		try {
   160 			return getSkupiny(sqlSkupinyVšechny, null);
   161 		} catch (NamingException | SQLException e) {
   162 			throw new BadRealmException("Chyba při zjišťování seznamu všech skupin.", e);
   163 		}
   164 	}
   165 
   166 	public Enumeration<String> getSkupiny(String sql, String parametr) throws SQLException, NamingException {
   167 		Collection<String> skupiny = new ArrayList<>();
   168 
   169 		Connection s = null;
   170 		PreparedStatement ps = null;
   171 		ResultSet rs = null;
   172 		try {
   173 			s = getSpojení();
   174 
   175 			ps = s.prepareStatement(sql);
   176 			if (parametr != null) {
   177 				ps.setString(1, parametr);
   178 			}
   179 			rs = ps.executeQuery();
   180 
   181 			while (rs.next()) {
   182 				skupiny.add(rs.getString(1));
   183 			}
   184 
   185 			return Collections.enumeration(skupiny);
   186 		} finally {
   187 			zavri(s, ps, rs);
   188 		}
   189 	}
   190 
   191 	/**
   192 	 *
   193 	 * @param jméno uživatelské jméno
   194 	 * @param heslo heslo
   195 	 * @return true pokud je jméno a heslo v pořádku | false nikdy – vyhazuje výjimku
   196 	 * @throws LoginException pokud je jméno nebo heslo neplatné nebo došlo k jiné chybě
   197 	 */
   198 	public boolean ověřUživatele(String jméno, char[] heslo) throws LoginException {
   199 
   200 		Connection s = null;
   201 		PreparedStatement ps = null;
   202 		ResultSet rs = null;
   203 		try {
   204 			s = getSpojení();
   205 			ps = s.prepareStatement(sqlHeslo);
   206 			ps.setString(1, jméno);
   207 			ps.setString(2, new String(heslo));
   208 			rs = ps.executeQuery();
   209 
   210 			if (rs.next()) {
   211 				String dbJméno = rs.getString(1);
   212 				if (dbJméno != null && dbJméno.equals(jméno)) {
   213 					// OK – úspěšné ověření
   214 					return true;
   215 				} else {
   216 					throw new LoginException("Špatné heslo nebo neexistující uživatel: " + jméno + " != " + dbJméno);
   217 				}
   218 			} else {
   219 				throw new LoginException("Špatné heslo nebo neexistující uživatel: " + jméno);
   220 			}
   221 		} catch (NamingException | SQLException e) {
   222 			String hláška = "SQL chyba při ověřování hesla uživatele: " + jméno + ".";
   223 			_logger.log(Level.WARNING, hláška, e);
   224 			throw new LoginException(hláška);
   225 		} finally {
   226 			zavri(s, ps, rs);
   227 		}
   228 	}
   229 
   230 	/**
   231 	 * Zavře všechno
   232 	 *
   233 	 * @param spojeni DB spojení
   234 	 * @param prikaz DB dotaz
   235 	 * @param vysledek DB výsledek
   236 	 */
   237 	private static void zavri(Connection spojeni, Statement prikaz, ResultSet vysledek) {
   238 		if (vysledek != null) {
   239 			try {
   240 				vysledek.close();
   241 			} catch (Exception e) {
   242 				_logger.log(Level.FINE, "Při zavírání SQL výsledku došlo k chybě.", e);
   243 			}
   244 		}
   245 		if (prikaz != null) {
   246 			try {
   247 				prikaz.close();
   248 			} catch (Exception e) {
   249 				_logger.log(Level.FINE, "Při zavírání SQL příkazu došlo k chybě.", e);
   250 			}
   251 		}
   252 		if (spojeni != null) {
   253 			try {
   254 				spojeni.close();
   255 			} catch (Exception e) {
   256 				_logger.log(Level.FINE, "Při zavírání SQL spojení došlo k chybě.", e);
   257 			}
   258 		}
   259 	}
   260 }