在这个小教程中,我将向您展示如何构建一个负责注册和登录的 GWT 模块。
密码使用 Sha256 进行哈希处理并加盐。
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 文件。
不要忘记将你的 jars 添加到你的构建路径中。
将此添加到您的 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 放入 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
创建一个用于登录的模块。模块名称“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();
}
}
用户现在可以在此应用程序上注册。但是 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 的视图。