【shiro】初识shiro身份与权限验证

依赖导入

<dependencies>  
 <dependency>
     <groupId>org.apache.shiro</groupId>  
     <artifactId>shiro-core</artifactId>  
     <version>1.3.2</version>  
 </dependency>  
 <dependency>
      <groupId>commons-logging</groupId>  
     <artifactId>commons-logging</artifactId>  
     <version>1.2</version>  
 </dependency>  
 <dependency> 
     <groupId>junit</groupId>  
     <artifactId>junit</artifactId>  
     <version>4.12</version>  
 </dependency> 
 <dependency> 
     <groupId>org.hamcrest</groupId>  
     <artifactId>hamcrest-core</artifactId>  
     <version>1.3</version>  
 </dependency> 
 <dependency> 
     <groupId>org.slf4j</groupId>  
     <artifactId>slf4j-log4j12</artifactId>  
     <version>1.7.25</version>  
 </dependency>
 </dependencies>

配置文件

log4j.properties

# Configure logging for testing: optionally with log file  
log4j.rootLogger=WARN, stdout  
# log4j.rootLogger=WARN, stdout, logfile  

log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n  

log4j.appender.logfile=org.apache.log4j.FileAppender  
log4j.appender.logfile.File=target/spring.log  
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout  
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

myshiro.ini (后期可以通过其他方式配置)

[main]  
myRealm=MyDefaultRealm  
securityManager.realms=$myRealm

MyDefaultRealm是自定义的Realm对象包全路径


介绍

通过shiro登陆验证的流程,如下图

我们先通过Subject,这是一个门面对象去“迎接”登陆的用户的信息,
然后把这些信息交给Shiro的核心SecurityManager的核心管理

Realm数据源,用于保存从数据库获取的数据对象


自定义Realm

import org.apache.shiro.authc.*;  
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;  
import org.apache.shiro.authz.AuthorizationInfo;  
import org.apache.shiro.authz.SimpleAuthorizationInfo;  
import org.apache.shiro.realm.AuthorizingRealm;  
import org.apache.shiro.subject.PrincipalCollection;  
import org.apache.shiro.util.ByteSource;  

import java.util.Arrays;  

/**  
 * @ClassName MyDefaultRealm  
 * @Description TODO  
  * @Author xufeng  
 * @Data 2019/3/18 16:58  
 * @Version 1.0  
 **/public class MyDefaultRealm extends AuthorizingRealm {  

    public MyDefaultRealm(){  
        HashedCredentialsMatcher passwordMatcher = new HashedCredentialsMatcher("md5");  

  passwordMatcher.setHashIterations(3);  
 this.setCredentialsMatcher(passwordMatcher);  
  }  

    //授权  
  @Override  
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
        String myName = (String) principals.getPrimaryPrincipal();  
  SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
  info.addRole("admin");  
  info.addStringPermissions(Arrays.asList("add","delete"));  

 return info;  
  }  

    //验证  
  @Override  
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
        String userName  = (String) token.getPrincipal();  

  String tSalt = "盐值123";  

 if ("xff".equals(userName)){  
            throw new UnknownAccountException();  
  }  

        return new SimpleAuthenticationInfo(userName, getPasswordFromDB(userName), generateSalt(tSalt), getName());  
  }  

    private String getPasswordFromDB(String userName) {  
        //do SomeThing  
  return "4d7461d62743c1e852f96ac24a6d1767";  
  }  

    private ByteSource generateSalt(String salt) {  
        return ByteSource.Util.bytes(salt);  
  }  

}

doGetAuthorizationInfo是授权,在这个方法里查询数据库,获取用户权限并放入权限对象返回

doGetAuthenticationInfo是验证,在该方法里通过用户名从数据库查询密码,然后与token中的用户密码校验,成功会返回登陆信息,失败会抛出异常

可以在该类的构造器中声明密码的加密方法,以什么方式加密、盐值,以及,循环多少次


编写测试类

import org.apache.shiro.SecurityUtils;  
import org.apache.shiro.authc.AuthenticationException;  
import org.apache.shiro.authc.UsernamePasswordToken;  
import org.apache.shiro.config.IniSecurityManagerFactory;  
import org.apache.shiro.crypto.hash.Md5Hash;  
import org.apache.shiro.subject.Subject;  
import org.apache.shiro.util.Factory;  
import org.junit.Assert;  
import org.junit.Test;  
import org.apache.shiro.mgt.SecurityManager;  

/**  
 * @ClassName MyTest  
 * @Description TODO  
  * @Author xufeng  
 * @Data 2019/3/18 16:31  
 * @Version 1.0  
 **/public class MyTest {  

    @Test  
  public void test() {  

      Factory<SecurityManager> factory  = new IniSecurityManagerFactory("classpath:myshiro.ini");  
      SecurityManager sm = factory.getInstance();  
      SecurityUtils.setSecurityManager(sm);  

      Subject subject = SecurityUtils.getSubject();  
      UsernamePasswordToken token = new UsernamePasswordToken("jay","123456");  

     try{  
                subject.login(token);  
      }catch (AuthenticationException a){  
                System.out.println("登陆失败");  
      }  
            //断言用户已经登陆  
      Assert.assertEquals(true, subject.isAuthenticated());  

      System.out.println(subject.hasRole("admin"));  

      subject.checkRole("admin");  

      System.out.println(subject.isPermitted("add"));  

      System.out.println(subject.isPermittedAll("add","delete"));  

      System.out.println(subject.isPermitted("update"));  

      System.out.println(subject.getPrincipal().toString());  

      subject.logout();  

      System.out.println(subject.getPrincipal().toString());  
  }  

    @Test  
  public void tt(){  
      Md5Hash md5Hash = new Md5Hash("123456","盐值123",3);  
      System.out.println(md5Hash);  
  }  
}

方法内前面三行是引入shiro配置,创建subject门面对象,通过login方法验证token

在subject.login(token)这行打个断点,可以看到token里的值如下:

下面是对用户权限的验证,最后打印结果为:

true
true
true
false
jay

java.lang.NullPointerException
    at MyTest.test(MyTest.java:52)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

logout后subject对象会被销毁,抛出空指针异常。