导语
之前的分享中,了解到了用户身份认证,在说用户认证的时候提到了一个概念就是Realm,在之前的入门分享中提到了,Realm其实就是一个安全数据源,那么怎么样使用这个安全数据源呢?下面就来一起研究一下
文章目录
Realm概念
Realm 域,Shiro从Realm获取安全数据(例如用户、角色、权限等)。之前提到SecurityManager需要进行身份验证就必须从Realm中获取到一个合法的用户身份,从而比较用户身份是否合法,同时在SecurityManager获取身份的同时Realm也需要维护一套用户身份用来判断用户是否能执行某项操作。可以把Realm看作是一个DataSource。也就是上面提到的安全数据源。
在之前的例子中的ini配置文件就可以看做是一个安全数据源
Realm接口源码
public interface Realm {
//返回一个唯一的Realm名字
String getName();
//判断此Realm是否支持此Token
boolean supports(AuthenticationToken token);
//根据Token获取认证信息
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}
从上面代码中看到Realm最为重要的一个事情就是根据一个Token获取的认证的信息。
Realm如何使用
在之前有简单的提到,Realm 使用的时候可以是单个也可以是多个。下面就来分别Realm的不同使用方式。
单个Realm配置
1、自定Realm的实现com.nihui.shiro.realm.MyRealm类中
public class MyRealm implements Realm {
public String getName() {
return "myrealm";
}
public boolean supports(AuthenticationToken token) {
//仅仅支持一个UsernamePaasswordToke
return token instanceof UsernamePasswordToken;
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"nihui".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)){
//密码错误
throw new IncorrectCredentialsException();
}
//如果身份认证验证成功,返回一个AuthenticationInfo
return new SimpleAuthenticationInfo(username,password,getName());
}
}
2、ini 配置文件指定自定义的Realm
# 声明一个realm
myRealm=com.nihui.shiro.realm.MyRealm
# 指定SecurityManager的Realm实现
securityManager.realms=$myRealm
3、测试效果
public class SRealeTest {
public static void main(String[] args) {
//1、获取SecurityManager工厂,
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
//2、得到一个SecurityManager实例,绑定到SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//得到Subject 以及用户名密码的身份验证Token
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("nihui","123");
// 验证登陆
try {
subject.login(token);
}catch (AuthenticationException e){
//身份认证失败
}
System.out.println(subject.isAuthenticated()); //true表示用户已经登陆
//退出操作
subject.logout();
}
}
多个Realm配置
多个Realm只需要将配置文件,改为如下的内容分别实现两个Realm就可以了。
# 声明realm
# 声明realm
myRealm1=com.nihui.shiro.realm.realmconfig.MyRealm1
myRealm2=com.nihui.shiro.realm.realmconfig.MyRealm2
# 指定SecurityManager的Realm实现
securityManager.realms=$myRealm1,$myRealm2
按照SecurityManager会按照realms指定的顺序进行身份认证,按照上面配置文件中指定的顺序,来进行Realm的认证,如果删除了最后指定的securityManager.realms,那么会按照Realm声明的顺序来进行匹配。下面 就来测试一下
Realm1 规则用户名admin,密码123
public class MyRealm1 implements Realm {
public String getName() {
return "myrealm1";
}
public boolean supports(AuthenticationToken token) {
//仅仅支持一个UsernamePaasswordToke
return token instanceof UsernamePasswordToken;
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"admin".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)){
//密码错误
throw new IncorrectCredentialsException();
}
//如果身份认证验证成功,返回一个AuthenticationInfo
return new SimpleAuthenticationInfo(username,password,getName());
}
}
Realm2 规则用户名nihui,密码1234
public class MyRealm2 implements Realm {
public String getName() {
return "myrealm2";
}
public boolean supports(AuthenticationToken token) {
//仅仅支持一个UsernamePaasswordToke
return token instanceof UsernamePasswordToken;
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"nihui".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if (!"1234".equals(password)){
//密码错误
throw new IncorrectCredentialsException();
}
//如果身份认证验证成功,返回一个AuthenticationInfo
return new SimpleAuthenticationInfo(username,password,getName());
}
}
测试类
public class MultiRealmTest {
public static void main(String[] args) {
//1、获取SecurityManager工厂,
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");
//2、得到一个SecurityManager实例,绑定到SecurityUtils
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//得到Subject 以及用户名密码的身份验证Token
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("nihui","1234");
// UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
// 验证登陆
try {
subject.login(token);
}catch (AuthenticationException e){
//身份认证失败
}
System.out.println(subject.isAuthenticated()); //true表示用户已经登陆
//退出操作
subject.logout();
}
}
这个时候如果将配置文件改为如下,会发现它是按照默认顺序进行匹配的并且两个内容都进行了匹配。
# 声明realm
myRealm1=com.nihui.shiro.realm.realmconfig.MyRealm1
myRealm2=com.nihui.shiro.realm.realmconfig.MyRealm2
# 指定SecurityManager的Realm实现
#securityManager.realms=$myRealm1,$myRealm2
如果将配置文件改为如下的内容效果如何呢?这时候就会发现只有一个nihui的规则被进行了匹配,没有指定的admin的规则则没有被匹配到。
# 声明realm
myRealm1=com.nihui.shiro.realm.realmconfig.MyRealm1
myRealm2=com.nihui.shiro.realm.realmconfig.MyRealm2
# 指定SecurityManager的Realm实现
securityManager.realms=$myRealm1
Shiro默认提供的Realm
在org.apache.shiro.realm包中提供了默认的一些Realm实现,主要有如下一些内容
一般情况下在使用扩展的时候只需要继承AuthorizingRealm(授权)就可以了,AuthorizingRealm继承了AuthenticatingRealm(身份验证),同时也间接的继承了CachingRealm(带有缓存的实现)。在上面类中比较重要的几个默认实现。
在实际中的使用
org.apache.shiro.realm.text.IniRealm
[users]部分指定了用户名密码以及其角色,[roles]部分指定角色信息具体配置可以参考官网IniRealm配置
[users]
nihui=123
test=123
admin=123
org.apache.shiro.realm.text.PropertiesRealm
通过配置文件的形式来指定。
private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
private static final String USERNAME_PREFIX = "user.";
private static final String ROLENAME_PREFIX = "role.";
private static final String DEFAULT_RESOURCE_PATH = "classpath:shiro-users.properties";
org.apache.shiro.realm.jdbc.JdbcRealm
通过SQL查询响应的信息,例如从用户表中查询用户信息,从角色表中查询角色信息。
/**
* The default query used to retrieve account data for the user.
*/
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
/**
* The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
*/
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
/**
* The default query used to retrieve the roles that apply to a user.
*/
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
/**
* The default query used to retrieve permissions that apply to a particular role.
*/
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
自定义Realm实现
通过继承AuthorizingRealm类来实现自定义的Realm实现。
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private AdminService adminService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在实际项目中,这里可以根据实际情况做缓存操作。如果没有缓存操作,Shiro有自己的时间隔离机制,2分钟不会重复执行该方法。
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//获取到用户账号通过用户账号获取到用户信息
String account = (String) token.getPrincipal();
// UserAdmin userInfo = adminService.findByUsername(account);
Admin userInfo =adminService.findByUsername(account);
if (userInfo==null){
throw new UnknownAccountException();
}
if ("0".equals(userInfo.getStatus().toString())){
throw new LockedAccountException();
}
SecurityUtils.getSubject().getSession().setAttribute("user",userInfo);
ByteSource credentialsSalt = ByteSource.Util.bytes(userInfo.getPublic_salt());
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo,
userInfo.getPassword(),
credentialsSalt,
getName());
return authenticationInfo;
}
//这方法是用来做权限认证的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
总结
通过上面的内容了解到了Realm作为关键数据节点在Shiro中的存在,并且介绍了关于Realm的类继承关系,了解了Realm的存在价值,如何使用Realm。