4

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

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

4

1 回答 1

6

下载和安装

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

下载后将 shiro-all-1.2.2.jar 包含在您的 lib 文件夹中。 在此处输入图像描述

我们还可以包含我们稍后需要的其他 .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)

不要忘记将你的 jars 添加到你的构建路径中。

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 模块

创建一个用于登录的模块。模块名称“Login”和包名称“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> 

登录服务.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);
}

登录服务实现

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 不知道如何将加盐密码与给定的用户输入进行比较。为此,我们需要实现我们自己的 Realm。Realm 本质上是一个特定于安全性的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 的视图。

于 2013-06-18T19:20:43.893 回答