使用shiro进行认证通常不会将用户信息直接写在ini文件中,目前假设从某数据源中获取用户信息,密码暂不加密一切从简。获取数据的这一步操作就要交给realm(域)来进行。
而认证是由securityManager指派认证器来完成,所以需要将自定义的realm交给securityManager。
首先,实现一个自定义的realm,没有继承AuthorizingRealm,而是直接实现realm接口(因为教程上是这么做的,这个应该是按照需求而决定)。
public class SelfRealm implements Realm { @Override public String getName() { return "SelfRealm"; //realm的名字 } @Override public boolean supports(AuthenticationToken authenticationToken) { return authenticationToken instanceof UsernamePasswordToken; //支持的token类型 } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String username = (String) authenticationToken.getPrincipal(); String password = new String((char[])authenticationToken.getCredentials()); if(!"joker".equals(username)) { //模拟从数据源获取用户名 throw new UnknownAccountException(); } if(!"shiro".equals(password)) { //模拟从数据源获取密码 throw new IncorrectCredentialsException(); } return new SimpleAuthenticationInfo(username, password, getName()); } }
测试类
public class Test { @org.junit.Test public void test() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //创建核心管理器 securityManager.setRealm(new SelfRealm()); //将自定义的realm交给核心管理器 SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); //获取主体 UsernamePasswordToken token = new UsernamePasswordToken("joker", "shiro"); //创建token subject.login(token); //登录 } }
进行认证时会调用getAuthenticationInfo方法,UsernamePasswordToken是AuthenticationToken的实现类,subject.isAuthenticated()可以获取认证的状态,true为成功,false为失败。
以上为单realm配置,在shiro还支持多realm配置,可以配置在ini文件中。
#声明realm FirstRealm=全类名 SecondRealm=全类名 #配置securityManager的realms securityManager.realms=$FirstRealm,$SecondRealm
会按照realm的配置顺序进行认证,如果没有再ini文件中配置,则会按照声明顺序进行认证,当在ini文件中配置了realm时,没有写入配置的realm会被忽略。
那是否可以不通过配置文件来设置realm呢?
接下来我自己动手实践了一下,首先对自定义realm进行改造。
public class SelfRealm implements Realm { Integer id; public SelfRealm(Integer id) { //创建实例对象时,为对象设置一个id this.id = id; } @Override public String getName() { return "SelfRealm"; } @Override public boolean supports(AuthenticationToken authenticationToken) { return authenticationToken instanceof UsernamePasswordToken; } @Override public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println(id); //在执行认证操作时,将id打印到控制台 String username = (String) authenticationToken.getPrincipal(); String password = new String((char[])authenticationToken.getCredentials()); if(!"joker".equals(username)) { throw new UnknownAccountException(); } if(!"shiro".equals(password)) { throw new IncorrectCredentialsException(); } return new SimpleAuthenticationInfo(username, password, getName()); } }
然后将两个仅仅是id不同的realm交给securityManager,观察是否会按照设置的先后顺序进行执行?是否两个realm都会被执行?
测试方法部分改造如下:
securityManager.setRealm(new SelfRealm(0)); securityManager.setRealm(new SelfRealm(2));
但是结果是id为2的realm被执行,而id为0的realm对象没有被执行,看来应该是被id为2的覆盖掉了。
接下来我进入了源码看了一下,发现了setRealms方法,setRealms(Collection<Realm> realms),可以传入集合参数,这波应该是稳了。
public class Test { @org.junit.Test public void test() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); List<Realm> selfRealmList = new ArrayList<>(); selfRealmList.add(new SelfRealm(0)); selfRealmList.add(new SelfRealm(2)); securityManager.setRealms(selfRealmList); SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("joker", "shiro"); try { subject.login(token); } catch (Exception e) {} System.out.println(subject.isAuthenticated()); } }
果然这次两个realm都被执行,并且是按照添加进集合的顺序执行的。
接下来教程中提到了一些关于自定义realm的常用做法和shiro提供的默认realm:
一般继承AuthorizingRealm,它继承了AuthenticatingRealm(身份验证),而且也间接继承了CachingRealm(带有缓存实现)。
主要默认实现有:IniRealm、PropertiesRealm、JdbcRealm。
接下在教程中介绍了一下JdbcRealm的使用,以及三种认证策略,没有找到用代码设置认证策略的方式,所以此处暂不记录。