Version 5

    Chinese Version: 开发基于JBoss AS 7.2.0的Java EE程序 - 05.如何配Security Domain以及如何用JAAS进行身份认证和权限控制 or  ( part.1 + part.2)

     

     

    Summary

     

    In JAAS, there are concepts Identity and Role. The relation between them is N:N. Namely, One Identity can owns many Roles and One Role can be assigned to many Identities. For example:

    User1 owns Role1,Role2,Role3,Role4.

    User2 owns Role1,Role2,Role3,Role5

    User3 owns Role3,Role4,Role5

    User4 owns Role3,Role4

    User5 owns Role5

    ...

    Advantage: it is simple and direct.

    Disadvantage: If there are many users and many roles, the user-role management becomes tedious and fallible.

     

    **********************************************************

     

    In our real life, we often assign roles to users through usergroup:

    Create UserGroup1, UserGroup2, UserGroup3...

    Assign Role(s) to UserGroup(s)

    Assign UserGroup(s) to User.

    For example:

    UserGroup1 owns Role1,Role2,Role3,
    UserGroup2 owns Role3,Role4,
    UserGroup3 owns Role5
    User1 belongs to UserGroup1 and UserGroup2, so he owns Role1,Role2,Role3,Role4
    User2 belongs to UserGroup1 and UserGroup3, so he owns Role1,Role2,Role3,Role5
    User3 belongs to UserGroup2 and UserGroup3, so he owns Role3,Role4,Role5
    User4 belongs to UserGroup2, so he owns Role3,Role4
    User5 belongs to UserGroup3, so he owns Role5

    Namely:

    User1 owns Role1,Role2,Role3,Role4。
    User2 owns Role1,Role2,Role3,Role5
    User3 owns Role3,Role4,Role5
    User4 owns Role3,Role4
    User5 owns Role5

    The effect of this method is same as the first method. But, it is more manageable.

    Of course, you can use the first method if you prefer it.

     

     

    In most case, we should save JAAS Info. into DB or LDAP (instead of text file). So we must

    (a) Create Entity beans to represent user, usergroup and role.

    (b) Configure <login-module> in standalone.xml to tell JBoss AS how to read JAAS info. from DB

    (c) Authenticate user through html pages.

    Let's describe those topics one by one in following sections.

     

     

     

     

    1. Entities

     

    1.1 Relation among JAAS related EJB Entities

     

    Let's demonstrate the relation with this figure:

            

     

     

    1.2 EJB Entity Beans

    (a) JaasRole.java

    import java.io.Serializable;

     

    import javax.persistence.*;

     

    @Entity
    public class JaasRole implements Serializable {
        //*********************************[static fields]*********************************//
        private static final long serialVersionUID = 1L;
        //JPA
        public static final String JPA_GEN_NAME = "GEN_JAASROLE";
        public static final String JPA_PK_COL_VAL = "NEXT_PK_JAASROLE";
       
       
        //*********************************[member fields]*********************************//
        private Integer id;
        private String name;
        private String displayName = "";

     

        //*********************************[getter/setter]*********************************//
        @Id
        @TableGenerator(name = JPA_GEN_NAME,
            table=JPATableGeneratorHelper.TABLE_JPA_GENERATOR,
            pkColumnName=JPATableGeneratorHelper.PK_COL_NAME,
            valueColumnName=JPATableGeneratorHelper.VALUE_COL_NAME,
            allocationSize=1,
            pkColumnValue=JPA_PK_COL_VAL
        )
        @GeneratedValue(strategy = GenerationType.TABLE, generator=JPA_GEN_NAME)
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }   
       
        @Column(unique=true)
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
       
        @Column(nullable=false)
        public String getDisplayName() {
            return displayName;
        }
        public void setDisplayName(String displayName) {
            this.displayName = displayName;
        }
       
       
        //*********************************[Transient]*********************************//
        @Override
        @Transient
        public String toString(){
            return "{Name:"+displayName+";Jaas Role:"+name+"}";
        }
    }

     

     

    (b) UserGroup.java

     

    @Entity

    public class UserGroup implements Serializable {

        //*********************************[static fields]*********************************//

        private static final long serialVersionUID = 1L;

        //JPA

        public static final String JPA_GEN_NAME = "GEN_USERGROUP";

        public static final String JPA_PK_COL_VAL = "NEXT_PK_USERGROUP";

        //JOIN TABLE

        public static final String JOIN_TABLE_UserGroup_JaasRole = "UserGroup_JaasRole";

        public static final String JOIN_TABLE_UserGroup_JaasRole_JoinColumn = "usergroup_id";

        public static final String JOIN_TABLE_UserGroup_JaasRole_InverseJoinColumn = "jaasrole_id";

        //*********************************[member fields]*********************************//

        private Integer id;

        private String name;

        private Set<JaasRole> jaasRoles;

       

       

        //*********************************[getter/setter]*********************************//

        @Id

        @TableGenerator(name = JPA_GEN_NAME,

            table=JPATableGeneratorHelper.TABLE_JPA_GENERATOR,

            pkColumnName=JPATableGeneratorHelper.PK_COL_NAME,

            valueColumnName=JPATableGeneratorHelper.VALUE_COL_NAME,

            allocationSize=1,

            pkColumnValue=JPA_PK_COL_VAL

        )

        @GeneratedValue(strategy = GenerationType.TABLE, generator=JPA_GEN_NAME)

        public Integer getId() {

            return id;

        }

        public void setId(Integer id) {

            this.id = id;

        }

       

        public String getName() {

            return name;

        }

        @Column(unique=true)

        public void setName(String name) {

            this.name = name;

        }

     

     

        @ManyToMany(targetEntity=JaasRole.class,fetch = FetchType.LAZY)

        @JoinTable(name               = JOIN_TABLE_UserGroup_JaasRole,

                   joinColumns        = @JoinColumn(name=JOIN_TABLE_UserGroup_JaasRole_JoinColumn),

                   inverseJoinColumns = @JoinColumn(name=JOIN_TABLE_UserGroup_JaasRole_InverseJoinColumn)

            )

        public Set<JaasRole> getJaasRoles() {

            return jaasRoles;

        }

        public void setJaasRoles(Set<JaasRole> jaasRoles) {

            this.jaasRoles = jaasRoles;

        }

       

       

        //*********************************[Transient]*********************************//

        @Override

        @Transient

        public String toString(){

            return "{Name:"+name+"}";

        }

    }

     

    (c) User.java

     

    @Entity

    public class User implements Serializable{

        //*********************************[static fields]*********************************//

        private static final long serialVersionUID = 1L;

        //JPA

        public static final String JPA_GEN_NAME = "GEN_USER";

        public static final String JPA_PK_COL_VAL = "NEXT_PK_USER";

        //JOIN TABLE

        public static final String JOIN_TABLE_User_UserGroup = "User_UserGroup";

        public static final String JOIN_TABLE_User_UserGroup_JoinColumn = "user_id";

        public static final String JOIN_TABLE_User_UserGroup_InverseJoinColumn = "usergroup_id";

     

        //*********************************[member fields]*********************************//

        private Long id;

        private Date createDate = new Date();

        private String username;//登录名

        private String password;//加了Transient注解:仅仅供用户从客户端传递到服务器端以便新增或者修改时使用。

        private String hashedPassword;

        private String firstname = "";//真实姓

        private String lastname = "";//真实名       

        private String mobilePhone = "";//手机

        private String email ="";//电子邮件。

        private Date birthday = null;//生日

        private Gender gender = Gender.UNKNOWN;//性别

        private Integer maxConcurrentSessions = 1;//EJB客户端的最大并发会话数量。

        @Deprecated

        private String imageLink = "unknown.jpg";//作废!portraitDir取代!头像<img src="#{request.contextPath}/file/people/#{user.portraitDir}/48.png" />

        private Integer score = 100;//论坛积分。

        private Boolean activated = false;

        private Boolean locked = false;//用户是否因为谩骂、发广告等不良行为被锁定

        private Date lockReleaseDate = new Date();//解锁日期

        private Date lastChangePasswordDate = new Date();

        private Long portraitDir = 0L;//肖像存储的子目录地址,如果为0,表明用户没用设置过肖像,那么采用默认的肖像目录:0。如果用户设置了肖像,那么更新这个数字为ID值!

        private Set<UserGroup> userGroups;//java.util.List被替换成java.util.Set,参见warning.txt

       

       

        //*********************************[getter/setter]*********************************//

        @Id

        @TableGenerator(name = JPA_GEN_NAME,

            table=JPATableGeneratorHelper.TABLE_JPA_GENERATOR,

            pkColumnName=JPATableGeneratorHelper.PK_COL_NAME,

            valueColumnName=JPATableGeneratorHelper.VALUE_COL_NAME,

            allocationSize=1,

            pkColumnValue=JPA_PK_COL_VAL

        )

        @GeneratedValue(strategy = GenerationType.TABLE, generator=JPA_GEN_NAME)

        public Long getId() {

            return id;

        }

        public void setId(Long id) {

            this.id = id;

        }

     

        @Temporal(TemporalType.TIMESTAMP)

        @Column(nullable=false,updatable=false)

        public Date getCreateDate() {

            return createDate;

        }

        public void setCreateDate(Date createDate) {

            this.createDate = createDate;

        }

       

        @Column(unique=true,nullable=false,updatable=false,length=USERNAME_MAX_LENGTH)

        public String getUsername() {

            return username;

        }

        public void setUsername(String username) {

            this.username = username;

        }

     

        /**

         * 16 bytes = 128 bit

         * 64 bytes = 512 bit

         * 128 bytes = 1024 bit

         */   

        @Column(nullable=false,length=256)
        public String getHashedPassword() {
            return hashedPassword;
        }
        public void setHashedPassword(String hashedPassword) {
            this.hashedPassword = hashedPassword;
        }

     

        //Other Getter/Setter

        @ManyToMany(targetEntity=UserGroup.class,fetch = FetchType.LAZY)

        @JoinTable(name               = JOIN_TABLE_User_UserGroup,

                   joinColumns        = @JoinColumn(name=JOIN_TABLE_User_UserGroup_JoinColumn),

                   inverseJoinColumns = @JoinColumn(name=JOIN_TABLE_User_UserGroup_InverseJoinColumn)

            )

        public Set<UserGroup> getUserGroups() {

            return userGroups;

        }

        public void setUserGroups(Set<UserGroup> userGroups) {

            this.userGroups = userGroups;

        }

       

       

        //*********************************[Transient]*********************************//

        @Transient

        public String getPassword() {

            return password;

        }

       

        public void setPassword(String password) {

            this.password = password;

        }

       

    }

     

     

    1.3 Tables in DB(Generated automatically by JBoss AS 7)

     

            

     

            

     

            

     

            

     

            

     

     

    2. security-domain configuration in standalone.xml

     

    <management>
        <security-realms>
            <security-realm name="ManagementRealm">
                ...
            </security-realm>
            <security-realm name="ApplicationRealm">
                <server-identities>
                    <ssl>
                        <keystore path="server.keystore" relative-to="jboss.server.config.dir" keystore-password="ybxiang_keystore_password"/>
                    </ssl>

                </server-identities>
                <authentication>
                    <jaas name="ybxiang-forum-jaas-security-domain"/>
                </authentication>
            </security-realm>
        </security-realms>
        ...
    </management>

    ...

    <subsystem xmlns="urn:jboss:domain:security:1.2">

        <security-domains>

            <security-domain name="ybxiang-forum-jaas-security-domain" cache-type="default">

                <authentication>

                    <login-module code="Remoting" flag="optional">

                        <module-option name="password-stacking" value="useFirstPass"/>

                    </login-module>

                    <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">

                        <module-option name="password-stacking" value="useFirstPass"/>

                        <module-option name="dsJndiName" value="java:jboss/datasources/ybxiangForumMySqlDataSource"/>

                        <module-option name="principalsQuery" value="SELECT hashedPassword FROM User WHERE username=? and activated=true and locked=false"/>

                        <module-option name="rolesQuery" value="SELECT DISTINCT r.name, 'Roles' FROM User u, User_UserGroup ug, UserGroup_JaasRole gr, JaasRole r WHERE u.id=ug.user_id AND ug.usergroup_id=gr.usergroup_id AND gr.jaasrole_id=r.id AND u.username=?"/>

                        <module-option name="hashAlgorithm" value="SHA-256"/>

                        <module-option name="hashEncoding" value="Base64"/>

                        <module-option name="hashCharset" value="UTF-8"/>

                        <module-option name="unauthenticatedIdentity" value="guest"/>

                    </login-module>

                </authentication>

            </security-domain>

            <security-domain name="other" cache-type="default">

                ...

            </security-domain>

            <security-domain name="jboss-web-policy" cache-type="default">

                ...

            </security-domain>

            <security-domain name="jboss-ejb-policy" cache-type="default">

                ...

            </security-domain>

        </security-domains>

    </subsystem>

     

     

    3. Security domain references in JavaEE sub-projects

     

    We should configure bellow 3 files so that our Java EE sub-projects can use the security domain defined above:

    • jboss-as-7.2.0.Final\standalone\deployments\ybxiang-forum.ear\META-INF\jboss-app.xml
    • jboss-as-7.2.0.Final\standalone\deployments\ybxiang-forum.ear\ybxiang-forum-ejb.jar\META-INF\jboss-ejb3.xml
    • jboss-as-7.2.0.Final\standalone\deployments\ybxiang-forum.ear\ybxiang-forum-war.war\WEB-INF\jboss-web.xml

     

    3.1 jboss-app.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <p:jboss-app xmlns:p="http://www.jboss.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee ../../xsd/jboss-app_7_0.xsd ">
        <security-domain>ybxiang-forum-jaas-security-domain</security-domain>
    </p:jboss-app>

     

    3.2 jboss-ejb3.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:security="urn:security"
        xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
        version="3.1"
        impl-version="2.0">
       
        <assembly-descriptor xmlns="http://java.sun.com/xml/ns/javaee">
            <security:security xmlns:security="urn:security">
                <security:security-domain>ybxiang-forum-jaas-security-domain</security:security-domain>
                <ejb-name>*</ejb-name>

            </security:security>
        </assembly-descriptor>
    </jboss:ejb-jar>

     

    3.3 jboss-web.xml

    <?xml version="1.0" encoding="UTF-8"?>

    <jboss-web>

        <security-domain>java:/jaas/ybxiang-forum-jaas-security-domain</security-domain>

        <!-- encoding for login servlet 'j_security_check' -->

        <valve>

            <class-name>org.apache.catalina.authenticator.FormAuthenticator</class-name>

            <param>

                <param-name>characterEncoding</param-name>

                <param-value>UTF-8</param-value>

            </param>

        </valve>

    </jboss-web>

     

     

    Next part: JBoss AS 7.2.0 - Java EE application development - 05.How to configure Security domain and JAAS - part.2