如何在Apache Shiro中使用GWT进行哈希和盐腌

时间:2013-06-18 19:20:44

标签: mysql gwt salt shiro

在这个小教程中,我将向您展示如何构建一个负责注册和登录的GWT模块。

密码用Sha256进行哈希处理并加盐。

1 个答案:

答案 0 :(得分:6)

下载和安装

下载Apache Shiro:http://shiro.apache.org/download.html; 我使用了Shrio-All(1.2.2 Binary Distribution)http://tweedo.com/mirror/apache/shiro/1.2.2/shiro-root-1.2.2-source-release.zip

下载后,在lib文件夹中包含shiro-all-1.2.2.jar。 enter image description here

我们还可以包含我们稍后需要的其他.jar文件。

  1. MySQL驱动程序:http://www.java2s.com/Code/Jar/c/Downloadcommysqljdbc515jar.htm(com.mysql.jdbc_5.1.5.jar)
  2. SLF4J记录:http://www.slf4j.org/download.html(slf4j-api-1.7.5.jar,slf4j-simple-1.7.5.jar)
  3. Apache Commons Beanutils:http://repo2.maven.org/maven2/commons-beanutils/commons-beanutils/1.7.0/(commons-beanutils-1.7.0.jar)
  4. 不要忘记将你的罐子添加到你的构建路径。

    的web.xml

    将此添加到您的web.xml

    <!-- Apache Shero -->
    <listener>
      <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>
    <filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>
    <!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
    <!-- requests. Usually this filter mapping is defined first (before all others) to -->
    <!-- ensure that Shiro works in subsequent filters in the filter chain: -->
    <filter-mapping>
        <filter-name>ShiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
        <dispatcher>ERROR</dispatcher>
    </filter-mapping> 
    

    shiro.ini

    将shiro.ini放入WEB-INF:

    [main]
    authc.loginUrl = /Login.html?gwt.codesvr=127.0.0.1:9997
    authc.successUrl  = /Leitfaden.html
    logout.redirectUrl = /login.html
    
    # ------------------------
    # Database
    
    # Own Realm
    jdbcRealm = leitfaden.login.server.MyRealm
    
    # Sha256
    sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
    # base64 encoding, not hex in this example:
    sha256Matcher.storedCredentialsHexEncoded = false
    sha256Matcher.hashIterations = 1024
    
    jdbcRealm.credentialsMatcher = $sha256Matcher
    
    # User Query
    # default is "select password from users where username = ?"
    jdbcRealm.authenticationQuery = SELECT password, salt FROM USER WHERE email = ?
    
    # Connection 
    ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
    ds.serverName = localhost
    ds.user = root
    ds.password = root
    ds.databaseName = leitfaden
    jdbcRealm.dataSource=$ds
    
    authc.usernameParam = email
    authc.passwordParam = password
    authc.failureKeyAttribute = shiroLoginFailure
    
    # Use Built-in Chache Manager
    builtInCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
    securityManager.cacheManager = $builtInCacheManager
    
    # -----------------------------------------------------------------------------
    [urls]
    /yourMainUrl.html = authc
    

    GWT模块

    创建登录模块。模块名称“登录”和包名称“leitfaden.login”:

    将此添加到您的web.xml

    <servlet>
      <servlet-name>LoginService</servlet-name>
      <servlet-class>leitfaden.login.server.LoginServiceImpl</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>LoginService</servlet-name>
      <url-pattern>/leitfaden.login.Login/LoginService</url-pattern>
    </servlet-mapping> 
    

    <强> LoginService.java

    @RemoteServiceRelativePath("LoginService")
    public interface LoginService extends RemoteService {
        public Boolean isLoggedIn();
        public Boolean tryLogin(String email, String password, Boolean rememberMe);
        public void logout();
        public void registrate(String email, String password);
    }
    

    <强> LoginServiceAsync.java

    public interface LoginServiceAsync {
        public void isLoggedIn(AsyncCallback<Boolean> callback);
        public void tryLogin(String email, String password, Boolean rememberMe, AsyncCallback<Boolean> callback);
        public void logout(AsyncCallback<Void> callback);
        public void registrate(String email, String password, AsyncCallback<Void> callback);
    }
    

    <强> LoginServiceImpl

    public class LoginServiceImpl extends RemoteServiceServlet implements LoginService {
    
        private static final long serialVersionUID = -4051026136441981243L;
        private static final transient Logger log = LoggerFactory
                .getLogger(LoginServiceImpl.class);
    
        private org.apache.shiro.subject.Subject currentUser;
    
        public LoginServiceImpl() {
            Factory<SecurityManager> factory = new IniSecurityManagerFactory();
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
        }
    
        @Override
        public Boolean isLoggedIn() {
            currentUser = SecurityUtils.getSubject();
    
            if (currentUser.isAuthenticated()) {
                return true;
            } else {
                return false;
            }
        }
    
        @Override
        public Boolean tryLogin(String username, String password, Boolean rememberMe) {
            // get the currently executing user:
            currentUser = SecurityUtils.getSubject();
    
            // let's login the current user so we can check against roles and
            // permissions:
            if (!currentUser.isAuthenticated()) {
                 //collect user principals and credentials in a gui specific manner 
                //such as username/password html form, X509 certificate, OpenID, etc.
                //We'll use the username/password example here since it is the most common.
                UsernamePasswordToken token = new UsernamePasswordToken(username,password);
                 //this is all you have to do to support 'remember me' (no config - built in!):
                token.setRememberMe(rememberMe);
    
                try {
                    currentUser.login(token);
                    log.info("User [" + currentUser.getPrincipal().toString() + "] logged in successfully.");
                    return true;
                } catch (UnknownAccountException uae) {
                    log.info("There is no user with username of "
                            + token.getPrincipal());
                } catch (IncorrectCredentialsException ice) {
                    log.info("Password for account " + token.getPrincipal()
                            + " was incorrect!");
                } catch (LockedAccountException lae) {
                    log.info("The account for username " + token.getPrincipal()
                            + " is locked.  "
                            + "Please contact your administrator to unlock it.");
                } catch (AuthenticationException ae) {
                    log.error(ae.getLocalizedMessage());
                }
            }
    
            return false;
        }
    
        @Override
        public void logout() {
            currentUser = SecurityUtils.getSubject();
            currentUser.logout();
        }
    
        @Override
        public void registrate(String email, String plainTextPassword) {
            RandomNumberGenerator rng = new SecureRandomNumberGenerator();
            Object salt = rng.nextBytes();
    
            // Now hash the plain-text password with the random salt and multiple
            // iterations and then Base64-encode the value (requires less space than Hex):
            String hashedPasswordBase64 = new Sha256Hash(plainTextPassword, salt,1024).toBase64();
    
            User user = new User(email, hashedPasswordBase64, salt.toString(), 0);
            this.createUser(user);
        }
    
        private void createUser(User user) {
            UserDAL.connect();
    
            UserDAL.beginTransaction();
            new UserDAL().createUser(user);
            log.info("User with email:" + user.getEmail() + " hashedPassword:"+ user.getPassword() + " salt:" + user.getSalt());
            UserDAL.commitTransaction();
    
            UserDAL.disconnect();
        }
    
    }
    

    MyRealm.java

    用户现在可以在此应用程序中注册。但Shiro不知道如何将盐渍密码与给定的用户输入进行比较。为此我们需要实现我们自己的领域。领域本质上是特定于安全性的DAO

    MyRealm.java使用给定的电子邮件获取用户并返回SaltedAuthenticationInfo。有了SaltedAuthenticationInfo,Shiro知道如何将用户输入与数据库中的用户进行比较。

    public class MyRealm extends JdbcRealm {
        private static final Logger log = LoggerFactory.getLogger(MyRealm.class);
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // identify account to log to
            UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
            final String username = userPassToken.getUsername();
    
            if (username == null) {
                log.debug("Username is null.");
                return null;
            }
    
            // read password hash and salt from db
            final PasswdSalt passwdSalt = getPasswordForUser(username);
    
            if (passwdSalt == null) {
                log.debug("No account found for user [" + username + "]");
                return null;
            }
    
            // return salted credentials
            SaltedAuthenticationInfo info = new MySaltedAuthentificationInfo(username, passwdSalt.password, passwdSalt.salt);
    
            return info;
        }
    
        private PasswdSalt getPasswordForUser(String username) {
            User user = getUserByEmail(username);
            if (user == null) {
                return null;
            }
            return new PasswdSalt(user.getPassword(), user.getSalt());
        }
    
        private User getUserByEmail(String email) {
            UserDAL.connect();
            User user = new UserDAL().getUserByEmail(email);
            UserDAL.disconnect();
            return user;
        }
    
        class PasswdSalt {
            public String password;
            public String salt;
    
            public PasswdSalt(String password, String salt) {
                super();
                this.password = password;
                this.salt = salt;
            }
        }
    
    }
    

    MySaltedAuthentificationInfo 重要的是你在getCredentialsSalt()中正确解码了盐。我使用过Base64。

    public class MySaltedAuthentificationInfo implements SaltedAuthenticationInfo {
    
        private static final long serialVersionUID = -2342452442602696063L;
    
        private String username;
        private String password;
        private String salt;
    
        public MySaltedAuthentificationInfo(String username, String password, String salt) {
            this.username = username;
            this.password = password;
            this.salt = salt;
        }
    
        @Override
        public PrincipalCollection getPrincipals() {
            PrincipalCollection coll = new SimplePrincipalCollection(username, username);
            return coll;
        }
    
        @Override
        public Object getCredentials() {
            return password;
        }
    
        @Override
        public ByteSource getCredentialsSalt() {
            return  new SimpleByteSource(Base64.decode(salt)); 
        }
    
    }
    

    用户现在可以注册并登录。您只需要在登录模块中编写调用LoginService的视图代码。