【Shiro】shiro自定義Realm
shiro中realm的是進行認證和授權的組件,自帶了幾種實現,比如jdbcRealm和iniRealm,實際項目中肯定都是自己實現realm。
1. 創建數據庫表
創建⽤戶表,⻆⾊表,權限表,以及對應的中間表。
create table t_user(
id int primary key auto_increment,
username varchar(20) not null unique,
password varchar(100) not null
)engine=innodb default charset=utf8;
create table t_role(
id int primary key auto_increment,
role_name varchar(50) not null unique,
create_time timestamp not null
)engine=innodb default charset=utf8;
create table t_permission(
id int primary key auto_increment,
permission_name varchar(50) not null unique,
create_time timestamp
)engine=innodb default charset=utf8;
create table t_user_role(
id int primary key auto_increment,
user_id int references t_user(id),
role_id int references t_role(id),
unique(user_id,role_id)
)engine=innodb default charset=utf8;
create table t_role_permission(
id int primary key auto_increment,
permission_id int references t_permission(id),
role_id int references t_role(id),
unique(permission_id,role_id)
)engine=innodb default charset=utf8;
2. Dao層和Service層
- User
package com.siyi.dao;
import com.siyi.pojo.User;
import org.apache.ibatis.annotations.Param;
public interface UserDao {
public User queryUserByUsername(@Param("username") String username);
}
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.siyi.dao.UserDao">
<select id="queryUserByUsername" parameterType="string" resultType="User">
select id,username,password
from t_user
where username = #{username}
</select>
</mapper>
package com.siyi.service;
import com.siyi.pojo.User;
import org.apache.ibatis.annotations.Param;
public interface UserService {
public User queryUserByUsername(@Param("username") String username);
}
package com.siyi.service.impl;
import com.siyi.dao.UserDao;
import com.siyi.pojo.User;
import com.siyi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public User queryUserByUsername(String username) {
return userDao.queryUserByUsername(username);
}
}
其他的role和permission都類似。
package com.siyi.dao;
import org.apache.ibatis.annotations.Param;
import java.util.Set;
public interface RoleDao {
public Set<String> queryAllRolenameByUsername(@Param("username") String username);
}
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.siyi.dao.RoleDao">
<select id="queryAllRolenameByUsername" parameterType="string" resultType="string">
select t_role.role_name
from t_user
join t_user_role on t_user.id = t_user_role.user_id
join t_role on t_role.id = t_user_role.role_id
where t_user.username=#{username}
</select>
</mapper>
package com.siyi.dao;
import org.apache.ibatis.annotations.Param;
import java.util.Set;
public interface PermissionDao {
public Set<String> queryAllPermissionByUsername(@Param("username") String username);
}
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.siyi.dao.PermissionDao">
<select id="queryAllPermissionByUsername" parameterType="string" resultType="string">
select distinct t_permission.permission_name
from t_user
join t_user_role on t_user.id = t_user_role.user_id
join t_role on t_role.id = t_user_role.role_id
join t_role_permission on t_role.id = t_role_permission.role_id
join t_permission on t_role_permission.permission_id = t_permission.id
where t_user.username = #{username};
</select>
</mapper>
3. 自定義Realm
Realm的職責是,爲shiro加載用戶,角色,權限數據,以供shiro內部校驗。之前定義在ini中的數據,默認有IniRealm去加載。現在是庫中的數據,需要⾃定義Realm去加載。
- 沒必要在Realm中定義⼤量的查詢數據的代碼,可以爲Realm定義好查詢數據的DAO和Service。
如下是Realm接口的所有⼦類,其中IniRealm是默認的Realm,負責加載shiro.ini中的[users]和[roles]信息,當shiro需要⽤戶,⻆⾊,權限信息時,會通過IniRealm獲得。
自定義realm有兩個⽗類可以選擇:
- 如果realm只負責做身份認證 ,則可以繼承:AuthenticatingRealm
- 如果realm要負責身份認證和權限校驗,則可以繼承:AuthorizingRealm
doGetAuthenticationInfo:查看身份信息:登陸的使用,subject.login()時使用
Authentication:認證的意思
doGetAuthorizationInfo:查看權限信息
Authorization:授權的意思
自定義MyRealm繼承AuthorizingRealm,分別實現認證和授權的方法。
doGetAuthenticationInfo是認證的方法,當用戶登陸的時候會調用。
doGetAuthorizationInfo是授權的方法,在攔截器中進行權限校驗的時候會調用。
public class MyRealm extends AuthorizingRealm {
/**
* 是否⽀持某種token
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
System.out.println("is support in realm1");
if(token instanceof UsernamePasswordToken){
return true;
}
return false;
}
/**
查看身份信息:登陸的使用
* 當subject.login()時,shiro會調⽤Realm的此⽅法做⽤戶信息的查詢,然後做校驗
* 職責:通過⽤戶傳遞來的⽤戶名查詢⽤戶表,獲得⽤戶信息
* 返回值:將查到的⽤戶信息(⽤戶名+密碼)封裝在AuthenticationInfo對象中返回
* 異常:如果沒有查到⽤戶可拋出⽤戶不存在異常;如果⽤戶被鎖定可拋出⽤戶被鎖異常;或其它⾃定義異常.
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();//獲得⽤戶名
System.out.println("user:"+username+" is authenticating~~");
UserService userService = (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
//身份認證
User user = userService.queryUserByUsername(username);
System.out.println("user:"+user);
/**
如下代碼可以省略,如果查詢結果爲空,直接返回null即可,
shiro的後續流程已有類似判斷邏輯,也會拋出UnknownAccountException
if(user==null){//如果⽤戶信息⾮法,則拋出異常
System.out.println("⽤戶不存在");
throw new UnknownAccountException("username:"+username+"不存在");
}
**/
//省略如上代碼後,可以直接寫:
if(user == null){
return null;
}
// 將 當前⽤戶的認證信息存⼊ SimpleAuthenticationInfo 並返回
// 注意此⽅法的本職⼯作就是查詢⽤戶的信息,所以查到後不⽤⽐對密碼是否正確,那是shiro後續流程的職責。
// 如果密碼錯誤,shiro的後續流程中會拋出異常IncorrectCredentialsException
return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
/**
補充: 可以在user表中增加⼀列,⽤於存儲⽤戶是否被鎖定,則查詢的User對象中會有是否鎖定的屬性
如果發現鎖定則可以在此⽅法中拋出異常:LockedAccountException,
**/
}
/**
* 當觸發權限或⻆⾊校驗時:subject.isPermitted() / subject.checkPermission();
* subject.hasRole() / subject.checkRole() 等。
* 此時需要數據庫中的 權限和⻆⾊數據,shiro會調⽤Realm的此⽅法來查詢
* ⻆⾊和權限信息存⼊SimpleAuthorizationInfo對象
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();//獲得username
//新建SimpleAuthorizationInfo對象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//查詢當前⽤戶的所有 "⻆⾊" 和 "權限"
UserService userService =(UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
Set<String> roles = userService.queryAllRolenameByUsername(username);
Set<String> perms = userService.queryAllPermissionByUsername(username);
//“⻆⾊” 和 “權限” 存⼊ SimpleAuthorizationInfo對象
info.setRoles(roles);
info.setStringPermissions(perms);
//返回SimpleAuthorizationInfo
return info;
}
}
在securityManager中注入realm,其中authenticator必須先於realms注入。
4. 配置Realm
shiro.ini中 配置⾃定義Realm
注意:[users] [roles] 兩個部分不再需要
[main]
shiro.loginUrl = /login.jsp
shiro.unauthorizedUrl=/login.jsp
shiro.redirectUrl=/logout.jsp
shiro.postOnlyLogout = true
#注意:此處實在安裝⾃定義Realm 指定realm
#聲明Realm 名稱 = Realm類路徑
realm = com.siyi.realm.MyRealm
#安裝Reaml 關聯到SecurityManager
securityManager.realms=$realm
[urls]
#照舊
web.xml配置不變
Shiro_⾃定義Realm後的 項⽬架構