前言
在之前我們已經對於Shiro這個安全管理框架有了一定的認識,同時也已經在SSM框架環境下對於Shiro進行了配置,下面我們來看看如何具體使用Shiro進行開發。
SSM框架
這裏簡單的說一下,因爲這裏使用的是Spring+Spring MVC+Mybatis框架,我是之前就搭好的。可能有些同學喜歡用Spring Boot來整合,這也是沒有問題的,後面有空會把Spring Boot的整合放上來。回到SSM框架上,這個應該算是java中比較經典的一個框架組合,會的同學不用說自己可以搭建,如果有不太清楚SSM的後面準備再寫一篇SSM框架的,大家一起學習一下。
下面是我項目的總體結構
還是中規中矩的Web層-Serivce層-Dao層,spring配置文件我根據不同的功能分開放置這樣更加清晰。
然後是Shiro的兩個文件,一個是自定義的Realm,用於驗證用戶和授予權限的;另一個是進行加密的。
開發中先寫一個註冊的功能用於添加用戶,簡單一點的話就是需要用戶名,密碼(密碼這裏用MD5+鹽)。這兩點是基本的。
下面是代碼的一個片段,這裏我是通過UUID生成了一個鹽對原密碼加密,然後就用到我們之前的加密用的文件
User user = new User();
String salt = UUIDUtil.createUUID().toString();
Date createTime = new Date();
Integer state = 0;
user.setUserName(userName);
user.setPassword(password);
user.setSalt(salt);
user.setCreateTime(createTime);
user.setState(state);
User newUser = passWordHelper.encryptPassword(user);
@Component
public class PassWordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
@Value("MD5")
private String algorithmName;
@Value("2")
private int hashIterations;
public void setRandomNumberGenerator(RandomNumberGenerator randomNumberGenerator) {
this.randomNumberGenerator = randomNumberGenerator;
}
public void setAlgorithmName(String algorithmName) {
this.algorithmName = algorithmName;
}
public void setHashIterations(int hashIterations) {
this.hashIterations = hashIterations;
}
public User encryptPassword(User user){
if(user.getSalt() == null || "".equals(user.getSalt())){
user.setSalt(randomNumberGenerator.nextBytes().toHex());
}
String newPassword = new SimpleHash(algorithmName,user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),hashIterations).toHex();
user.setPassword(newPassword);
return user;
}
}
這裏引用了加密方式和加密次數兩個參數,然後通過SimpleHash進行加密,得到一個加密後的密碼放入user對象中,然後在Serivce層中將新的user傳入Dao層添加到數據庫。ok,現在我們已經有一個用戶了,你也可以用別的方法,只要保證先添加一個用戶進來就對了。
下面來看一下Realm文件
public class myRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
/**
* @description 爲當前登錄的用戶授予角色和權限
* @author zhou
* @created 2018/10/17 15:15
* @param
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//獲取用戶名
String userName = (String)principalCollection.getPrimaryPrincipal();
Session session = SecurityUtils.getSubject().getSession();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//查詢用戶的role
Set<String> roles = roleService.findRoleByUserName(userName);
authorizationInfo.setRoles(roles);
//查詢用戶的permission
Set<String> permissions = permissionService.findPermissionByUserName(userName);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
/**
* @description 驗證當前登錄的用戶
* @author zhou
* @created 2018/10/17 15:16
* @param
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
AuthenticationException {
//獲得用戶信息
String userName = (String)authenticationToken.getPrincipal();
//從數據庫中查找
User user = userService.findByUserName(userName);
if(user==null){
//賬號不存在
throw new UnknownAccountException();
}else if(user.getState() == 1){
//賬號鎖定
throw new LockedAccountException();
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUserName(),
user.getPassword(), ByteSource.Util.bytes(user.getSalt()),getName());
return simpleAuthenticationInfo;
}
}
首先新建一個自定義的Realm,然後繼承AuthorizingRealm,重寫其中的doGetAuthorizationInfo和doGetAuthenticationInfo兩個方法。
首先看一下授權的方法,這裏是通過用戶名去我們的數據庫中查詢該用戶的角色和權限,這裏用set集合去存放角色和權限。然後把這兩個信息存到authorizationInfo中,這樣在用戶被授權後,如果用戶要執行什麼操作,可以從中獲取數據來判斷用戶是否具有對應的權限。
然後是登錄驗證的方法,這個和平時寫的javaWeb的驗證方式比較類似,首先獲得用戶名去數據庫中查找用戶信息,然後做一些簡單的校驗比如是否存在用戶和用戶是否被鎖定等等。然後將用戶名,密碼,鹽和Realm的名稱。然後在登錄的時候會用到這些。下面是我的登錄代碼
@RequestMapping(value = "/login")
public WebResponse userLogin(@RequestParam("userName") String userName,@RequestParam("password") String password,
@RequestParam(value = "rememberMe",defaultValue = "false") boolean rememberMe,
HttpServletRequest request){
if(isEmpty(userName)||isEmpty(password)){
log.error("用戶名或密碼爲空");
return new WebResponse().error(401,null,"用戶名或密碼爲空");
}
//查詢角色
Set<String> roleList = roleService.findRoleByUserName(userName);
//查詢權限
Set<String> permissionList = permissionService.findPermissionByUserName(userName);
//獲得主體
Subject currentUser = SecurityUtils.getSubject();
//判斷用戶是否登陸
if(!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
try {
//token.setRememberMe(rememberMe);
//存入參數
request.getSession().setAttribute("token",userName);
request.getSession().setAttribute("role",roleList);
request.getSession().setAttribute("permission",permissionList);
currentUser.login(token);
} catch (UnknownAccountException uae) {
log.error("賬號不存在");
return new WebResponse().error(402, null, "賬號不存在");
} catch (IncorrectCredentialsException ice) {
log.error("密碼錯誤,請重試");
return new WebResponse().error(403, null, "密碼錯誤,請重試");
} catch (LockedAccountException lae) {
log.error("該賬號已被禁用,無法登陸");
return new WebResponse().error(404, null, "該賬號已被禁用,無法登陸");
} catch (AuthenticationException ae) {
log.error("未知錯誤");
return new WebResponse().error(405, null, "未知錯誤");
}
}
HashMap<String,Object> userMap = new HashMap<>();
userMap.put("msg",userName + "登陸成功");
userMap.put("role",roleList);
userMap.put("permission",permissionList);
return new WebResponse().ok(userMap);
}
這裏主要看這個login()方法,當程序判定該用戶未被認證時,將用戶名和密碼放入Token中然後調用login,此時就會用到之前realm中用戶驗證的方法,將用戶輸入的信息和數據庫中查到的信息進行比對校驗。
後面是一些捕獲異常比如賬號不存在,密碼錯誤,賬號鎖定等等。
以上就是Shiro權限登錄的一個流程和整合過程中所需要的文件。
不過就個人覺得由於Shiro框架封裝程度比較高,感覺看似隨意調用了幾個方法就實現了驗證和授權,其實底層做的工作和javaWeb中做的是類似的。後面有機會會從源碼的角度再次剖析一下Shiro的Realm的工作原理。