前言
本篇文章主要講解用戶登錄認證模塊,下節再細講權限。當然,最後筆者會分享整套源碼。由於涉及到前端部分,年尾將至,沒啥時間整那些,因此,提供的代碼只涉及到後臺,並且是封裝過的,複用性高(但是,天上不會掉餡餅,還是要多敲多看多理解)。沒有界面視圖,確實理解比較吃力,望各位多多見諒!!!
Shiro優勢(Thinking)
shiro認證方式,獲取前端用戶名和密碼,實例化對象UsernamePasswordToken(後續全部交由shiro處理),也就是令牌,再傳遞給Subject,Subject調用自定義AuthorizingRealm,最後將用戶的密碼和鹽值(下文講解)初始化到SimpleAuthenticationInfo對象即可。
權限授權方式,獲取認證用戶在shiro存儲信息,查詢該用戶相關角色和權限地址,賦值到SimpleAuthorizationInfo對象中。最後通過PermissionsAuthorizationFilter權限過濾器,判斷是否具有相關權限。
簡單來說,異曲同工,都是把認證或權限信息賦值到shiro指定對象,然後通過過濾器識別判斷。
View
check包:權限認證;factory包:登錄認證;filter包:過濾器。
Fighting
引入shiro相關jar(version:1.4.0)
<!--shiro依賴 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
登錄過濾器
/**
* @Description:登錄權限
* @author: fengjk
* @time:2017年7月19日 下午5:47:51
*/
public class LoginUserFilter extends UserFilter {
@Resource
UserMapper userMapper;
private static final Logger logger = LoggerFactory.getLogger(LoginUserFilter.class);
@Resource
OwnServerProperties ownServerProperties;
@Override
protected boolean isAccessAllowed(ServletRequest paramServletRequest,ServletResponse paramServletResponse, Object mappedValue) {
/**
* 業務代碼
*
*/
}
@Override
protected boolean onAccessDenied(ServletRequest request,ServletResponse response) throws Exception {
return super.onAccessDenied(request,response);
}
}
自定義Realm(關鍵類)
public class ShiroDbRealm extends AuthorizingRealm {
private static final Logger LOGGER = LoggerFactory.getLogger(ShiroDbRealm.class);
/**
* 登錄認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
IShiro shiroFactory = ShiroFactroy.me();
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
User user = shiroFactory.user(token.getUsername());
ShiroUser shiroUser = shiroFactory.shiroUser(user);
SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName());
return info;
}
/**
* 權限認證
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
IShiro shiroFactory = ShiroFactroy.me();
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
List<Long> roleList = shiroUser.getRoleList();
Set<String> permissionSet = new HashSet<>();
Set<String> roleNameSet = new HashSet<>();
for (Long roleId : roleList) {
List<String> permissions = shiroFactory.findPermissionsByRoleId(roleId);
if (permissions != null) {
for (String permission : permissions) {
if (ToolUtil.isNotEmpty(permission)) {
permissionSet.add(permission);
}
}
}
String roleName = shiroFactory.findRoleNameByRoleId(roleId);
roleNameSet.add(roleName);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionSet);
info.addRoles(roleNameSet);
return info;
}
/**
* 設置認證加密方式
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}
package com.kmob.powernetwork.operation.shiro.factory;
import java.util.ArrayList;
import java.util.List;
import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.kmob.powernetwork.operation.constant.ManagerStatus;
import com.kmob.powernetwork.operation.dao.MenuDao;
import com.kmob.powernetwork.operation.dao.UserMgrDao;
import com.kmob.powernetwork.operation.factory.ConstantFactory;
import com.kmob.powernetwork.operation.model.User;
import com.kmob.powernetwork.operation.shiro.ShiroUser;
import com.kmob.powernetwork.operation.util.Convert;
import com.kmob.powernetwork.operation.util.SpringContextHolder;
@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class ShiroFactroy implements IShiro {
@Autowired
private UserMgrDao userMgrDao;
@Autowired
private MenuDao menuDao;
public static IShiro me() {
return SpringContextHolder.getBean(IShiro.class);
}
@Override
public User user(String account) {
User user = userMgrDao.getByAccount(account);
// 賬號不存在
if (null == user) {
throw new CredentialsException();
}
// 賬號被凍結
if (user.getStatus() != ManagerStatus.OK.getCode()) {
throw new LockedAccountException();
}
return user;
}
@Override
public ShiroUser shiroUser(User user) {
ShiroUser shiroUser = new ShiroUser();
shiroUser.setId(user.getId()); // 賬號id
shiroUser.setAccount(user.getAccount());// 賬號
shiroUser.setDeptId(user.getDeptid()); // 部門id
shiroUser.setName(user.getName()); // 用戶名稱
Long[] roleArray = Convert.toLongArray(true,user.getRoleid());// 角色集合
List<Long> roleList = new ArrayList<Long>();
List<String> roleNameList = new ArrayList<String>();
for (Long roleId : roleArray) {
roleList.add(roleId);
roleNameList.add(ConstantFactory.me().getSingleRoleName(roleId));
}
shiroUser.setRoleList(roleList);
shiroUser.setRoleNames(roleNameList);
return shiroUser;
}
@Override
public List<String> findPermissionsByRoleId(Long roleId) {
List<String> resUrls = menuDao.getResUrlsByRoleId(roleId);
return resUrls;
}
@Override
public String findRoleNameByRoleId(Long roleId) {
return ConstantFactory.me().getSingleRoleTip(roleId);
}
@Override
public SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName) {
String credentials = user.getPassword();
// 密碼加鹽處理
String source = user.getSalt();
ByteSource credentialsSalt = new Md5Hash(source);
return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName);
}
}
doGetAuthenticationInfo方法爲登錄認證處理操作,通過UsernamePasswordToken獲取用戶名,再獲取數據庫中用戶信息,最後存儲到SimpleAuthenticationInfo對象中。
Coding
那麼,shiro是如何做登錄校驗呢?原理無非也是驗證密碼。
通過調用AuthenticatingRealm類中assertCredentialsMatch方法,將AuthenticationToken令牌對象與AuthenticationInfo用戶認證存儲信息對象做比較。
比較方法就是CredentialsMatcher接口中doCredentialsMatch(),而接口具體實現類就是SimpleCredentialsMatcher。
通過該類中equals()判斷,跟平時寫的比較方法一樣。而方法中比較的參數,又是從AuthenticationToken和AuthenticationInfo中getCredentials()獲取的,那麼,獲取的又是什麼內容呢?
可以看出,doCredentialsMatch()比較的參數,其實就是認證時候我們獲取到的用戶密碼。但是,有的讀者就會有個疑問,出現兩個用戶密碼相同的情況呢?
那就要靠“顏值”了,沒錯,就是密碼加鹽處理(唯一性),哈哈。上圖可以看到直接實例化shiro中Md5Hash獲取,傳參到SimpleAuthenticationInfo對象。
HashedCredentialsMatcher是SimpleCredentialsMatcher的子類,因此,上文提到的doCredentialsMatch()此處也會執行,而比較的內容就是token和info對象中的credentialsSalt加密鹽值。
總結
關於shiro的安全登錄認證講解到此,其實說了那麼多,用起來卻很方便,幾句代碼就搞定了。當然,能用又理解再好不過啦,哈哈。文章如有筆誤,請及時留言告知,萬分感謝!最後,歡迎大家加羣一起討論學習,QQ:583138104
源碼下載