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