六、自定义Realm
存在的问题:目前所有的 用户、角色、权限数据都在ini文件中,不利于管理。
实际项目开发中这些信息,应该在数据库中。所以需要为这3类信息建表
6.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;
6.2 自定义Realm
Realm的职责是,为shiro加载 用户,角色,权限数据,以供shiro内部校验。
之前定义在ini中的数据,默认有IniRealm去加载。
现在库中的数据,需要自定义Realm去加载。
ops : 没必要在Realm中定义大量的查询数据的代码,可以为Realm定义好查询数据的DAO和Service。
6.2.1 父类
如下是Realm接口的所有子类,其中IniRealm是默认的Realm,负责加载shiro.ini中的[users]和[roles]信息,当shiro需要用户,角色,权限信息时,会通过IniRealm获得。
自定义realm有两个父类可以选择:
1> 如果realm只负责做身份认证 ,则可以继承:AuthenticatingRealm
2> 如果realm要负责身份认证和权限校验,则可以继承:AuthorizingRealm
6.2.2 定义Realm
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.queryUser(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) {
//获得username
String username = (String)principals.getPrimaryPrincipal();
//新建SimpleAuthorizationInfo对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//查询当前用户的所有 "角色" 和 "权限"
UserService userService =
(UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
Set<String> roles = userService.queryRolesByUsername(username);
Set<String> perms = userService.queryPermissionsByUsername(username);
//“角色” 和 “权限” 存入 SimpleAuthorizationInfo对象
info.setRoles(roles);
info.setStringPermissions(perms);
//返回SimpleAuthorizationInfo
return info;
}
}
6.3 配置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类路径
realm1 = com.zhj.realm.MyRealm
realm2 = com.zhj.realm.MyRealm2
#安装Reaml 关联到SecurityManager
securityManager.realms=$realm1,$realm2
[urls]
#照旧
web.xml配置不变
Shiro_自定义Realm后的 项目架构 |
---|
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U0ZI0KTH-1570717342900)(mdpic/shiro_spring架构.jpg)] |
七 、加密
用户的密码是不允许明文存储的,因为一旦数据泄露,用户的隐私信息会完全暴露。
密码必须结果加密,生成密文,然后数据库中只存储用户的密码的密文。
在加密过程中需要使用到一些**“不可逆加密”**,如 md5,sha等
所谓不可逆是指:
- 加密函数A, 明文 “abc”, A(“abc”) = “密文”,不能通过 “密文” 反推出 “abc”,即使密文泄露密码仍然安全。
7.1 加密介绍
shiro支持hash(散列)加密,常见的如 md5, sha等
基本加密过程
md5(明文),sha(明文) 得到明文的密文,但明文可能比较简单导致密文容易被破解。
加盐加密过程
系统生成一个随机salt=“xxxxxx”, md5(明文+salt) ,sha(明文+salt),则提升了密文的复杂度。
加盐多次迭代加密过程
如果迭代次数为2,则加密2次: md5(明文+salt)=密文a , md5(密文a+salt)=最终密文
sha(明文+salt)=密文a , sha(密文a+salt)=最终密文
则进一步提升了密文的复杂度,和被破解的难度。
加密过程中建议使用salt,并指定迭代次数,迭代次数的建议值1000+
实例代码:
String password