【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有兩個⽗類可以選擇:

  1. 如果realm只負責做身份認證 ,則可以繼承:AuthenticatingRealm
  2. 如果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後的 項⽬架構

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章