最近在看Shiro安全框架,了解到了一些初级的应用,先记录一波shiro的认证和授权的简单应用,后续继续深入学习,再捋一捋shiro框架的架构和高级应用。
Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架,它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案。
Shrio是一款比较简单的安全框架,虽然Spring Security会简易很多,但是在我们一般的开发中已经足够保证安全了。
Shiro最简单的应用也是最重要的应用就是认证(Authentication)和授权(Authorization)。这两个单词很像,经常把他们看错了。认证就是验证用户登录信息是否正确,授权就是给予用户相应的权限。
这里还有一个概念,Realm,安全数据源,Shiro通过Realm获取用户的信息进行比较以确认身份,也可以通过Realm获取用户的权限。
下面写一个测试来验证认证和授权功能
导入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
1、测试登录认证
创建shiro-auth.ini配置文件
[users]
#模拟数据库中的用户
#数据格式 用户名=密码
admin=123456
hzm=111111
编写测试方法
public void testLogin() throws Exception{
//加载ini配置文件并创建SecurityManager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-auth.ini");
//获取securityManager实例
SecurityManager securityManager = factory.getInstance();
//将securityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
//创建主体(此时的主体还未经过认证)
Subject subject = SecurityUtils.getSubject();
/**
* 模拟登录,和传统等不同的是需要使用主体进行登录
*/
//构造主体登录的凭证(即用户名/密码,这里要跟配置文件里的用户密码比较)
UsernamePasswordToken upToken = new UsernamePasswordToken("admin","123456");
//登录
subject.login(upToken);
//验证是否登录成功
System.out.println("用户登录成功="+subject.isAuthenticated());
//登录成功获取数据
//getPrincipal 获取登录成功的安全数据
System.out.println(subject.getPrincipal());
}
测试结果
如果我们的用户名密码不正确,会报错
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=false] did not match the expected credentials.
at org.apache.shiro.realm.AuthenticatingRealm.assertCredentialsMatch(AuthenticatingRealm.java:600)
at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:578)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267)
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
at Test.testLogin(Test.java:24)
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)
2、测试授权
同样,我们通过配置的方法模拟数据库的权限,创建配置文件shiro-perm.ini
[users]
#模拟从数据库查询的用户
#数据格式 用户名=密码,角色1,角色2..
admin=123456,role1,role2
hzm=111111,role2
[roles]
#模拟从数据库查询的角色和权限列表
#数据格式 角色名=权限1,权限2
role1=user:save,user:update
role2=user:update,user.delete
测试方法
public void testPerm() throws Exception {
//加载ini配置文件创建SecurityManager
IniSecurityManagerFactory factory = new
IniSecurityManagerFactory("classpath:shiro-perm.ini");
//获取securityManager
SecurityManager securityManager = factory.getInstance();
//将securityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
//创建主体(此时的主体还为经过认证)
Subject subject = SecurityUtils.getSubject();
/**
* 模拟登录,和传统不同的是需要使用主体进行登录
*/
//构造主体登录的凭证(即用户名/密码,这里要跟配置文件里的用户密码比较)
//第一个参数:用户名,第二个参数:密码
UsernamePasswordToken upToken = new UsernamePasswordToken("hzm", "111111");
//主体登录
subject.login(upToken);
//用户认证成功之后才可以完成授权工作
boolean hasRole = subject.hasRole("role1");
boolean hasPerm = subject.isPermitted("user:update");
System.out.println("用户是否具有role1角色"+hasRole+",用户是否具有update权限=" + hasPerm);
}
测试结果
对照配置文件,我们可以发现hzm用户不具有role1角色,但是具有user:update权限,测试结果正确。
3、通过自定义的Realm来认证和授权
shiro中自定义的Realm都要继承AuthorizingRealm,并且要重写doGetAuthorizationInfo(授权)和doGetAuthenticationInfo(认证)才能实现自定义的功能。
首先我们写一个PermissionRealm类
public class PermissionRealm extends AuthorizingRealm {
@Override
public void setName(String name) {
super.setName("permissionRealm");
}
/**
* 授权:授权的主要目的就是查询数据库获取用户的所有角色和权限信息
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 从principals获取已认证用户的信息
String username = (String) principalCollection.getPrimaryPrincipal();
/**
* 正式系统:应该从数据库中根据用户名或者id查询
* 这里为了方便演示,手动构造
*/
// 模拟从数据库中查询的用户所有权限
List<String> permissions = new ArrayList<String>();
permissions.add("user:save");// 用户的创建
permissions.add("user:update");// 商品添加权限
// 模拟从数据库中查询的用户所有角色
List<String> roles = new ArrayList<String>();
roles.add("role1");
roles.add("role2");
// 构造权限数据
SimpleAuthorizationInfo simpleAuthorizationInfo = new
SimpleAuthorizationInfo();
// 将查询的权限数据保存到simpleAuthorizationInfo
simpleAuthorizationInfo.addStringPermissions(permissions);
// 将查询的角色数据保存到simpleAuthorizationInfo
simpleAuthorizationInfo.addRoles(roles);
return simpleAuthorizationInfo;
}
/**
* 认证:认证的主要目的,比较用户输入的用户名密码是否和数据库中的一致
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取登录的upToken
UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
//获取输入的用户名密码
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
/**
* 验证用户名密码是否正确
* 正式系统:应该从数据库中查询用户并比较密码是否一致
* 为了测试以及区分前两个配置,只要输入的密码为88888则登录成功
*/
if(!password.equals("88888")) {
throw new RuntimeException("用户名或密码错误");//抛出异常表示认证失败
}else{
//通过验证,构造安全数据
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
return info;
}
}
}
同样通过配置模拟Realm的使用过程
创建shiro-realm.ini文件
[main]
#声明realm
permReam=shiro.PermissionRealm
#注册realm到securityManager中
securityManager.realms=$permReam
测试方法
@Before
public void init() throws Exception{
//加载ini配置文件创建SecurityManager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
//获取securityManager
SecurityManager securityManager = factory.getInstance();
//将securityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
}
@Test
public void testRealm() throws Exception{
//创建主体(此时的主体还未经过认证)
Subject subject = SecurityUtils.getSubject();
//构造主体登录的凭证(即用户名/密码)
UsernamePasswordToken upToken = new UsernamePasswordToken("hzm","88888");
//主体登录,这里会调用realm的认证,认证通过后会调用授权方法
subject.login(upToken);
//登录成功验证是否具有role1角色
System.out.println("当前用户具有role1="+subject.hasRole("role1"));
//登录成功验证是否具有某些权限
System.out.println("当前用户具有user:save权限="+subject.isPermitted("user:save"));
}
测试结果
本文只是对shiro的部分功能的小demo,后续会继续学习shiro更高级的东西并总结分享。