使用shiro 由於公司的業務上的需求前後臺公用的一張表,要實現前臺用戶和後臺用戶的分離攔截需要書寫多個realm 用來驗證前臺用戶還是後臺用戶。直接上代碼
1.書寫一個自定的token UsernamePasswordUsertypeToken 繼承UsernamePasswordToken 用來判斷用戶類型UsernamePasswordUsertypeToken 多出一個字段用來區分用戶類型
package com.jscredit.zxypt.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* 拓展shiro 中的UsernamePasswordToken
* @author liyaqiang
*
*/
public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private String usertype ;
public String getUsertype() {
return usertype;
}
public void setUsertype(String usertype) {
this.usertype = usertype;
}
public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {
super(loginName, password);
this.usertype = usertype;
}
}
定義前臺的驗證的realm
public class UserLoginRealm extends AuthorizingRealm{
private final Logger LOGGER = LoggerFactory.getLogger(UserLoginRealm.class);
private static final String ALGORITHM = "MD5";
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private UserKzService userKzService;
public UserLoginRealm(){
super();
}
/**
* 爲當前登錄的Subject授予角色和權限 ,該方法的調用時機爲需授權資源被訪問時
* 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中默認並未啓用AuthorizationCache
* 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支持,則可靈活決定是否啓用AuthorizationCache
* 比如說這裏從數據庫獲取權限信息時,先去訪問Spring3.1提供的緩存,而不使用Shior提供的AuthorizationCache
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroLoginUser shiroLoginUser = (ShiroLoginUser) principals.fromRealm(getName()).iterator().next();
String username = shiroLoginUser.getAccount();
if (username != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查詢用戶授權信息
List<Permission> perList = permissionService.getShiro("O");
if (perList != null && perList.size() != 0) {
for (Permission permission : perList) {
info.addStringPermission(permission.getPmsnCode());
}
return info;
}
}
return null;
}
/**
* 驗證前臺當前登錄的Subject 本例中該方法的調用時機爲UserLoginController.login()方法中執行Subject.login()時
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
// 這個authcToken是從userLoginController裏面currentUser.login(token)傳過來的
UsernamePasswordUsertypeToken fronttoken = (UsernamePasswordUsertypeToken) authcToken;
LOGGER.debug("驗證當前Subject時獲取到token爲"
+ ReflectionToStringBuilder.toString(fronttoken, ToStringStyle.MULTI_LINE_STYLE));
User users = userService.getUserByName(fronttoken.getUsername());
UserKz userKz= userKzService.selectByPrimaryKey(users.getUserId());
String usertype =userKz.getUserType();
// Shiro完成對比邏輯,返回和令牌相關的正確的驗證信息,第一個參數填登錄用戶名,第二個參數填合法的登錄密碼
if (users != null&&usertype!="00") {
ShiroLoginUser shiroLoginUser = new ShiroLoginUser(users.getUserId(), users.getAccount(),userKz.getUserType());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroLoginUser,users.getPassword(), getName());
this.setSession("shiroLoginUser", shiroLoginUser);
return authcInfo;
} else {
throw new AuthenticationException();
}
// 沒有返回登錄用戶名對應的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常
}
/**
* 更新用戶授權信息緩存.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除所有用戶授權信息緩存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
@PostConstruct
public void initCredentialsMatcher() {// MD5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
setCredentialsMatcher(matcher);
}
/**
* 將一些數據放到ShiroSession中,以便於其它地方使用
*
* 比如Controller,使用時直接用HttpSession.getAttribute(key)就可以取到
*/
private void setSession(Object key, Object value) {
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
LOGGER.debug("Session默認超時時間爲[" + session.getTimeout() + "]毫秒");
if (null != session) {
session.setAttribute(key, value);
}
}
}
}
2.後臺realm
public class LoginRealm extends AuthorizingRealm {
private final Logger LOGGER = LoggerFactory.getLogger(LoginRealm.class);
private static final String ALGORITHM = "MD5";
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private UserKzService userKzService;
public LoginRealm() {
super();
}
/**
* 驗證當前登錄的Subject 本例中該方法的調用時機爲LoginController.login()方法中執行Subject.login()時
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
// 這個authcToken是從LoginController裏面currentUser.login(token)傳過來的
UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authcToken;
LOGGER.debug("驗證當前Subject時獲取到token爲"
+ ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
User users = userService.getUserByName(token.getUsername());
UserKz userKz =userKzService.selectByPrimaryKey(users.getUserId());
String usertype =userKz.getUserType();
// Shiro完成對比邏輯,返回和令牌相關的正確的驗證信息,第一個參數填登錄用戶名,第二個參數填合法的登錄密碼
if (users != null&&usertype.equals("00")) {
ShiroUser shiroUser = new ShiroUser(users.getUserId(), users.getAccount());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser,
users.getPassword(), getName());
this.setSession("shiroUser", shiroUser);
return authcInfo;
} else {
throw new AuthenticationException();
}
// 沒有返回登錄用戶名對應的SimpleAuthenticationInfo對象時,就會在LoginController中拋出UnknownAccountException異常
}
/**
* 爲當前登錄的Subject授予角色和權限 ,該方法的調用時機爲需授權資源被訪問時
* 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中默認並未啓用AuthorizationCache
* 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支持,則可靈活決定是否啓用AuthorizationCache
* 比如說這裏從數據庫獲取權限信息時,先去訪問Spring3.1提供的緩存,而不使用Shior提供的AuthorizationCache
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName()).iterator().next();
String username = shiroUser.getAccount();
if (username != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查詢用戶授權信息
List<Permission> perList = permissionService.getShiro("O");
if (perList != null && perList.size() != 0) {
for (Permission permission : perList) {
info.addStringPermission(permission.getPmsnCode());
}
return info;
}
}
return null;
}
/**
* 更新用戶授權信息緩存.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除所有用戶授權信息緩存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
@PostConstruct
public void initCredentialsMatcher() {// MD5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
setCredentialsMatcher(matcher);
}
/**
* 將一些數據放到ShiroSession中,以便於其它地方使用
*
* 比如Controller,使用時直接用HttpSession.getAttribute(key)就可以取到
*/
private void setSession(Object key, Object value) {
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
LOGGER.debug("Session默認超時時間爲[" + session.getTimeout() + "]毫秒");
if (null != session) {
session.setAttribute(key, value);
}
}
}
}
3.書寫總的管理realm
public class DefautModularRealm extends org.apache.shiro.authc.pam.ModularRealmAuthenticator {
private Map<String, Object> definedRealms;
/**
* 多個realm實現
*/
@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
return super.doMultiRealmAuthentication(realms, token);
}
/**
* 調用單個realm執行操作
*/
@Override
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {
// 如果該realms不支持(不能驗證)當前token
if (!realm.supports(token)) {
throw new ShiroException("token錯誤!");
}
AuthenticationInfo info = null;
try {
info = realm.getAuthenticationInfo(token);
if (info == null) {
throw new ShiroException("token不存在!");
}
} catch (Exception e) {
throw new ShiroException("用戶名或者密碼錯誤!");
}
return info;
}
/**
* 判斷登錄類型執行操作
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {
this.assertRealmsConfigured();
Realm realm = null;
UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authenticationToken;
//判斷是否是後臺用戶
if (token.getUsertype().equals("admin")) {
realm = (Realm) this.definedRealms.get("loginRealm");
}
else{
realm = (Realm) this.definedRealms.get("userloginRealm");
}
return this.doSingleRealmAuthentication(realm, authenticationToken);
}
/**
* 判斷realm是否爲空
*/
@Override
protected void assertRealmsConfigured() throws IllegalStateException {
this.definedRealms = this.getDefinedRealms();
if (CollectionUtils.isEmpty(this.definedRealms)) {
throw new ShiroException("值傳遞錯誤!");
}
}
public Map<String, Object> getDefinedRealms() {
return this.definedRealms;
}
public void setDefinedRealms(Map<String, Object> definedRealms) {
this.definedRealms = definedRealms;
}
}
4.書寫包含用戶類型的token繼承 UsernamePasswordToken
public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private String usertype ;
public String getUsertype() {
return usertype;
}
public void setUsertype(String usertype) {
this.usertype = usertype;
}
public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {
super(loginName, password);
this.usertype = usertype;
}
}
4.將配置寫入到shiro的配置文件中
<!-- 用戶授權信息Cache 緩存在本機內存,不支持集羣 -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
<!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證用戶登錄的類爲自定義的ShiroDbRealm.java -->
<bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm">
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證前臺用戶登錄的類爲自定義的ShiroDbRealm.java -->
<bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm">
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!--多個realm 的集中管理 -->
<bean id="defineModularRealmAuthenticator" class=" com.jscredit.zxypt.shiro.DefautModularRealm">
<property name="definedRealms">
<map>
<entry key="loginRealm" value-ref="loginRealm" />
<entry key="userloginRealm" value-ref="userloginRealm" />
</map>
</property>
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
</property>
</bean>
<!-- Shiro默認會使用Servlet容器的Session,可通過sessionMode屬性來指定使用Shiro原生Session -->
<!-- 即<property name="sessionMode" value="native"/>,詳細說明見官方文檔 -->
<!-- 這裏主要是設置自定義的單Realm應用,若有多個Realm,可使用'realms'屬性代替 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="authenticator" ref="defineModularRealmAuthenticator" />
<!-- <property name="realm" ref="loginRealm"/> -->
<property name="realms" >
<list>
<bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm" />
<bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm" />
</list>
</property>
<property name="cacheManager" ref="cacheManager"/>
</bean>
這樣就可以在controller 中根據前後臺傳入不同的type 值 ,然後總的realm 會根據當前類型來判斷執行哪個realm 進而執行不同的驗證。然後我們再在realm中進行前後臺用戶的區分。進而執行自己的業務邏輯啦