2013-06-18 1 views
4

В этом небольшом учебном пособии я покажу вам, как построить модуль GWT, который отвечает за регистрацию и вход в систему.Как использовать GWT с Apache Shiro хэшированный и соленый

Пароль получает хэширование с Sha256 и соленое.

+0

С каких пор SO является местом для отправки учебников? – koma

+1

Я провел много исследований, и мне потребовалось очень много времени, чтобы узнать, как работает с gwt. То же самое касается соленых паролей. Я не нашел что-то вроде моего учебника в Интернете. Поэтому я думал, что хочу поделиться своими знаниями. И вот что такое stackoverflow. Не так ли? –

+2

Лучше переформулировать ваше объявление в вопрос (который вы затем отвечаете сами). Как уже упоминалось, SO не является местом для выгрузки учебников или блога о ваших путешествиях по развитию. – Veger

ответ

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

После загрузки включите shiro-all-1.2.2.jar в свою папку lib. 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 Logging: http://www.slf4j.org/download.html (SLF4J-апи-1.7.5.jar, SLF4J-простой 1.7.5.jar)
  3. Apache Commons BeanUtils: http://repo2.maven.org/maven2/commons-beanutils/commons-beanutils/1.7.0/ (Викисклада BeanUtils-1.7.0.jar)

не забудьте добавить свои банки на путь сборки.

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 Сейчас

Пользователи могут зарегистрироваться в этом приложении. Но Сиро не знает, как сравнить засоленные пароли с данным пользователем. Для этого нам нужно реализовать свое собственное Царство. A 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.

+0

Учитывается ли это, что соль является двоичной строкой и может содержать символы '\ 0'? Особенно, что хранение в базе данных может быть проблемой, но я не могу это судить. – martinstoeckli

+0

Я об этом не думал. Но у меня не было проблем с хранением в базе данных. В MySQL он работал нормально. –