【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后的 项⽬架构

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