<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数据源,用于保存从数据库获取的数据对象
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对象会被销毁,抛出空指针异常。