OpenShift:「こんにちは、クラウド!」

これは、 OpenShiftをJavaホスティングとして使用する場合の注意の続きです。

前回、OpenShiftクラウドでアプリケーションを作成する方法を見つけました。 JBoss AS 7.1サーバーとgitリポジトリーによる無料ホスティングを提供しています。 それでは、通常の「hello、world」よりも少し複雑なものを書き、JBoss ASおよびJBoss Tools開発ツールの機能を使用してみましょう。





一般的なタスクの1つ:特定のリソースへのアクセスを許可されたユーザーのみに許可し、割り当てられた役割に従って分離する jbossに組み込まれたログインモジュール、つまりorg.jboss.security.auth.spi.DatabaseServerLoginModuleの実装を使用してこれを行うことが提案されています。 推測するのは難しくありません。この場合、ユーザーとそのロールはデータベースに保存されます。



データスキームは非常に簡単です。これらは、APP_USERテーブル(ユーザー)、APP_ROLE(ロールディレクトリ)、およびAPP_MEMBERSHIP(割り当てられたロール)です。これらを介して、最初の2つのテーブル間の多対多の関係が実装されます。





Webコンソールでmysql-5.1カートリッジを使用して新しいjbossas-7アプリケーションを作成し、Eclipseにインポートします。 Webパースペクティブに切り替える必要があります。 ほとんどの場合、インポート後すぐに、Javaリソースセクションにエラーが含まれているとマークされ、その理由が[問題]ウィンドウに表示されます。
Project configuration is not up-to-date with pom.xml. Run project configuration update
      
      



このアドバイスに従います。プロジェクトルートを選択し、Maven->プロジェクト構成の更新コンテキストメニューを呼び出して実行すると、エラーが消えます。



プロジェクトツリーを展開します。



ご覧のとおり、すでにjavaクラスとリソースのフォルダー、webappフォルダーのindex.htmlファイル、いくつかのjspファイル、記述子のあるWEB-INFディレクトリがあります。 health.jspファイルは(web.xml記述子からのヘルスサーブレットの説明と同様に)すぐに削除できます。ここにある理由は明らかではありません。 snoop.jspファイルは引き続き有用であり、アプリケーションに関する統計を表示します。

プロジェクトのルートにあるのは、単一の依存関係を持つpom.xmlです

 <dependency> <groupId>org.jboss.spec</groupId> <artifactId>jboss-javaee-6.0</artifactId> <version>1.0.0.Final</version> <type>pom</type> <scope>provided</scope> </dependency>
      
      





これにより、jbossに含まれるすべてのモジュールにアクセスできます(ライブラリ-Maven依存関係ブランチを展開すると、リスト全体を表示できます)。



サーバー構成のセットアップ



ここで、Eclipseによってインポートされなかったファイルが必要です。 .openshift / config / standalone.xmlのプロジェクトディレクトリにあり、名前が示すように、jbossサーバーインスタンスの設定を説明しています。 ここでEclipseで開きましょう(アプリケーションがローカルjbossサーバーでデバッグされる場合、サーバーフォルダーstandalone / configuration / standalone.xmlのファイルを使用して同様の操作を実行する必要があります)。



エンコード設定


データベースでロシア語の文字を使用するには、接続がUTF-8エンコードである必要があります。 したがって、データソース(この場合はMysqlDS)を見つけ、エンコードに関する情報を追加します。

 <connection-url>jdbc:mysql://${env.OPENSHIFT_DB_HOST}:${env.OPENSHIFT_DB_PORT}/${env.OPENSHIFT_GEAR_NAME}?characterEncoding=UTF-8</connection-url>
      
      







認証モジュールの構成


ここで、セキュリティドメインを作成します。たとえば、「app-auth」と名前を付けます。 サブシステム「urn:jboss:domain:security:1.1」を見つけて、ドメインの説明を追加する必要があります。

 <security-domain name="app-auth"> <authentication> <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required"> <module-option name="dsJndiName" value="java:jboss/datasources/MysqlDS"/> <module-option name="principalsQuery" value="select PWD from APP_USER where USER_NAME=? and ENABLED=1"/> <module-option name="rolesQuery" value="select r.ROLE_NAME, 'Roles' from APP_ROLE r, APP_MEMBERSHIP m, APP_USER u where r.ROLE_ID=m.ROLE_ID and m.USER_ID=u.USER_ID and u.USER_NAME=?"/> <module-option name="hashAlgorithm" value="SHA-1"/> <module-option name="hashEncoding" value="base64"/> </login-module> </authentication> </security-domain>
      
      





プロパティdsJndiName、principalsQuery、rolesQueryの目的は明らかだと思います。 最後の2つのプロパティは、パスワードハッシュがデータベースに保存されることを示しています。 これらのプロパティが削除された場合、パスワードは平文で保存する必要があります。これはデバッグには受け入れられますが、実際のデータで行うべきではありません。



アプリケーション設定:顔、セキュリティ、初期化



次の行をweb.xmlに追加します。
  <!-- JSF mapping --> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.xhtml</url-pattern> </servlet-mapping> <!-- security --> <login-config> <auth-method>FORM</auth-method> <realm-name>app-auth</realm-name> <form-login-config> <form-login-page>/login.xhtml</form-login-page> <form-error-page>/login.xhtml</form-error-page> </form-login-config> </login-config> <security-role> <role-name>Admin</role-name> </security-role> <security-role> <role-name>Manager</role-name> </security-role> <security-constraint> <web-resource-collection> <web-resource-name>Admin Part</web-resource-name> <url-pattern>/admin/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>Admin</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>All Users</web-resource-name> <url-pattern>/view/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <!--  --> <listener> <listener-class>my.app.jaas.Initializer</listener-class> </listener>
      
      







Mavenセットアップ:pom.xmlの追加の依存関係



pom.xmlを開き、依存関係を追加します。
  <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.0.1.Final</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.richfaces.core</groupId> <artifactId>richfaces-core-impl</artifactId> <version>4.2.2.Final</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.richfaces.ui</groupId> <artifactId>richfaces-components-ui</artifactId> <version>4.2.2.Final</version> <scope>runtime</scope> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.6</version> </dependency>
      
      







Java Persistenceを構成する



JPAをプロジェクトに追加します。 これを行うには、プロジェクトプロパティを開き、[プロジェクトファセット]セクションを見つけます。このセクションでは、JPAの反対側のボックスをオンにする必要があります。 persistence.xmlファイルが自動的に作成されます。 次に、このファイルでデータベースへのアクセスを構成するか、設定をhibernate.cfg.xmlに転送できます。 この場合、便利なグラフィカルインターフェイスが手元にあり、既存のデータベースからリバースエンジニアリングを行う機会もあるので、後者を好みます。

2番目の方法では、以下を行う必要があります。

-persistence.xmlでhibernate.cfg.xmlを参照:
persistence.xml
 <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="test"> <properties> <property name="hibernate.ejb.cfgfile" value="/hibernate.cfg.xml" /> </properties> </persistence-unit> </persistence>
      
      





-src / main / resourcesフォルダーに次の内容のhibernate.cfg.xmlファイルを追加します。
hibernate.cfg.xml
 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory name=""> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.format_sql">true</property> <property name="hibernate.show_sql">false</property> <property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.connection.datasource">java:jboss/datasources/MysqlDS</property> </session-factory> </hibernate-configuration>
      
      





hibernate.hbm2ddl.autoに注意してください。更新値を使用すると、モデルに合わせてデータスキームを自動的に更新できます。このデータベースに1つのDDL行を記述する必要はありません。

format_sqlとshow_sqlはデバッグ時に便利です。

Session Factoryエディターのブックマークは多くの設定を提供しますが、現時点では必要ありません。

この設定では、完了と見なすことができます。



データモデル



データは2つのクラスで説明されます。 多対多の関係は、セットごとに両側で説明されます。 接続ホストはAppUserになります(AppRoleはめったに変更されません。これはエンティティというよりも参照です)。

MySqlにはシーケンスまたは自動インクリメントがないため、ジェネレーターにはGenerationType.TABLE戦略が選択されます。 残りは、注釈から明らかだと思います。

Appuser.java
 import java.util.*; import javax.persistence.*; @ Entity @ Table(name = "APP_USER", uniqueConstraints = @ UniqueConstraint(columnNames = "USER_NAME")) public class AppUser implements java.io.Serializable { private Long userId; private String userName; private String displayName; private String pwd; private Boolean enabled; private Set<AppRole> roles = new HashSet<AppRole>(0); @ TableGenerator( name = "UserIdGen", table = "APP_GEN", pkColumnName = "GEN_NAME", pkColumnValue = "USER_ID", valueColumnName = "GEN_VAL", allocationSize = 10) @ Id @ Column(name = "USER_ID", nullable = false) @ GeneratedValue(strategy=GenerationType.TABLE, generator="UserIdGen") public Long getUserId() { return this.userId; } public void setUserId(Long userId) { this.userId = userId; } @ Column(name = "USER_NAME", nullable = false, length = 30) public String getUserName() { return this.userName; } public void setUserName(String userName) { this.userName = userName; } @ Column(name = "DISPLAY_NAME", length = 250) public String getDisplayName() { return this.displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } @ Column(name = "PWD", length = 30) public String getPwd() { return this.pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @ Column(name = "ENABLED") public Boolean getEnabled() { return this.enabled; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } @ ManyToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL) @ JoinTable( name = "APP_MEMBERSHIP", joinColumns = { @ JoinColumn(name = "USER_ID", nullable = false, updatable = false) }, inverseJoinColumns = { @ JoinColumn(name = "ROLE_ID", nullable = false, updatable = false) }) public Set<AppRole> getRoles() { return this.roles; } public void setRoles(Set<AppRole> roles) { this.roles = roles; } }
      
      





パスワードの長さは30です。これは、base64エンコードのSHA-1ダイジェスト(20バイト)に十分な長さです。

有効なフィールドにはブール型が指定され、すべてのサーバーがこれを理解するわけではありません(たとえば、FirebirdSQLではこの名前でドメインを作成する必要があります)が、MySqlはそれを問題なく解釈します。

AppRole.java
 import java.util.*; import javax.persistence.*; @ Entity @ Table(name = "APP_ROLE", uniqueConstraints = @ UniqueConstraint(columnNames = "ROLE_NAME")) public class AppRole implements java.io.Serializable { private Long roleId; private String roleName; private String displayName; private Set<AppUser> users = new HashSet<AppUser>(0); @ TableGenerator( name = "RoleIdGen", table = "APP_GEN", pkColumnName = "GEN_NAME", pkColumnValue = "ROLE_ID", valueColumnName = "GEN_VAL", allocationSize = 10) @ Id @ Column(name = "ROLE_ID", nullable = false) @ GeneratedValue(strategy=GenerationType.TABLE, generator="RoleIdGen") public Long getRoleId() { return this.roleId; } public void setRoleId(Long roleId) { this.roleId = roleId; } @ Column(name = "ROLE_NAME", length = 30) public String getRoleName() { return this.roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } @ Column(name = "DISPLAY_NAME", length = 250) public String getDisplayName() { return this.displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } @ ManyToMany(fetch = FetchType.LAZY, mappedBy = "roles") public Set<AppUser> getUsers() { return this.users; } public void setUsers(Set<AppUser> users) { this.users = users; } }
      
      





必要に応じて、AppGenクラスをプロジェクトに追加することもできます。これはAPP_GENジェネレーターテーブルに対応し、アプリケーションがレガシーSQLサーバーと連携できるようにします。 実際には、デフォルトでフィールドはテーブルAPP_GENに作成されます-常にサポートされているわけではない256文字の長さの主キーGEN_NAMEであり、この長さは注釈で明示的に指定することで短縮できます。 私にとっては、30文字で十分です(たとえば、Oracleのシーケンス名の長さを参照)。



アプリケーションの初期化



アプリケーションは、web.xmlで以前に指定されたクラスmy.app.jaas.Initializerによって初期化されます

Initializer.java
 @ ManagedBean public class Initializer implements ServletContextListener { private static final Logger log = Logger.getLogger(Initializer.class); @Override public void contextDestroyed(ServletContextEvent event) {} @Override public void contextInitialized(ServletContextEvent event) { loadData(); } @ PersistenceContext EntityManager em; private AppRole checkRole(String roleName, String displayName, Session session) { AppRole role = (AppRole)session.createCriteria(AppRole.class) .add(Restrictions.eq("roleName", roleName)) .uniqueResult(); if (role == null) { role = new AppRole(); role.setRoleName(roleName); role.setDisplayName(displayName); session.save(role); } return role; } private void loadData() { Session session = (Session) em.getDelegate(); AppRole adminRole = checkRole("Admin", "", session); checkRole("Manager", "", session); if (adminRole.getUsers().size()==0) { AppUser user = (AppUser)session.createCriteria(AppUser.class) .add(Restrictions.eq("userName", "admin")) .uniqueResult(); if(user==null) { user = new AppUser(); user.setUserName("admin"); user.setDisplayName(""); user.setPwd(encode("topsecret")); user.setEnabled(true); session.save(user); } adminRole.getUsers().add(user);//nothing user.getRoles().add(adminRole); session.save(adminRole); session.save(user); } session.flush(); session.close(); } public static String encode(String value) { //get the message digest try{ MessageDigest md = MessageDigest.getInstance("SHA"); //SHA-1 algorithm md.update(value.getBytes("UTF-8")); //byte-representation using UTF-8 encoding format byte raw[] = md.digest(); String hash = Base64.encodeBase64String(raw).trim(); return hash; } catch(Exception e) { log.error(e, e); } return value; } public String logout() { FacesContext ctx = FacesContext.getCurrentInstance(); HttpSession session = (HttpSession)ctx.getExternalContext().getSession(false); session.invalidate(); return("logout"); } }
      
      





ご覧のとおり、唯一のリスナーメソッドServletContextListener.contextInitializedが実装されています。このメソッドでは、ロールがチェックされ、必要に応じて作成され、少なくとも1人の管理者がチェックされます。 管理者がいない場合、管理者アカウントが作成されます。

静的エンコード方式は、ユーザー管理モジュールで使用できます。

また、明確な目的を持つ別の1 logout()メソッドも必要です。

この場合、データベースの操作はJPAではなく下位レベルで行われます。そのため、hibernate APIを使用すると、素晴らしいorg.hibernate.Criteriaインターフェースを使用して、sql、hql、またはjpqlの1行なしですべてのアクションを実行できます。



認証フォーム



login.xhtml
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:h="http://java.sun.com/jsf/html"> <h:head> <title>  </title> <h:outputStylesheet> div.login-container { width: 255px; position: relative; margin: 0 auto 0 auto; } </h:outputStylesheet> </h:head> <h:body> <div class="login-container" id="login_container"> <rich:panel> <f:facet name="header"> <h:outputText value="  " /> </f:facet> <form method="post" action="j_security_check" name="loginform" id="loginForm" target="_parent"> <h:panelGrid columns="2" cellpadding="2" columnClasses="right,left" width="100%"> <h:outputLabel for="j_username" value=":" /> <h:inputText style="width: 155px;" id="j_username" value="" /> <h:outputLabel for="j_password" value=":" /> <h:inputSecret style="width: 155px;" id="j_password" value="" /> <h:panelGroup /> <h:panelGroup /> <h:panelGroup /> <h:panelGroup> <h:commandButton name="login" id="login-submit" value="" /> <h:outputText value=" " escape="false"/> <h:commandButton type="button" id="login-cancel" value="" /> </h:panelGroup> </h:panelGrid> </form> </rich:panel> </div> <h:outputScript> (function(){ jQuery("#login_container").offset({top:Math.max(0,(jQuery(window).height()/2)-150)}); var el = jQuery("#j_username").get(0);el.focus();el.select(); })(); </h:outputScript> </h:body> </html>
      
      





ここでは、任意の形状を描画できます。唯一の要件は、j_usernameとj_passwordの値をサーバーに送信する必要があることです。 この場合はrichfacesコンポーネントが使用されるため、jQueryはページコードに自動的に含まれ、その機能はログインコンテナーを配置し、ユーザー名を持つ要素を自動的に選択するスクリプトで使用されます。



だから、すべてが最初の打ち上げの準備ができています。 次に、webapp / view、webapp / adminディレクトリにコンテンツを配置し、サーバーへの変更をコミットします。アプリケーションを起動した後、これらのディレクトリへのアクセスが認証後および対応するロールがある場合のみ可能になります。

アプリケーションが起動すると、必要なテーブルとレコードがデータベースに自動的に作成されます。これは、phpmyadminカートリッジをインストールするか、hibernate.cfg.xmlファイルでクエリトレースを有効にすることで確認できます。

  <property name="hibernate.show_sql">false</property>
      
      







結論



上記の例では、OpenShiftの認証アプリケーションの開発を検討しました。 同じアプリケーションをコンパイルして、他のJBoss AS 7.1サーバーでサポートされているSQLダイアレクトのいずれかで使用できます。 違いは、standalone.xml構成ファイルの場所と、目的のjdbcモジュールをインストールする必要があることだけです。

データソースへの接続を設定するときは、エンコーディングを覚えておいてください。

このテンプレートは最小限のロード可能なライブラリを使用しました。これは、OpenShift Expressが提供する限られたリソースにとって重要です。 ほとんどのモジュールはすでにJBossディストリビューションに含まれているため、ディスクスペースとアプリケーションの公開時間を節約できます。



All Articles