1.Shiro简介
ApacheShiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。
Shiro有三大核心组件:
- Subject :当前用户的操作。即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物
- SecurityManager:用于管理所有的Subject。即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。
- Realms:用于进行权限信息的验证。Realms则是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realms来自定义的管理我们自己系统内部的权限规则。
Shiro官方架构图片:
其中Authentication 和 Authorization 在shiro的用户权限认证过程中其通过两个方法来实现:
1、Authentication:是验证用户身份的过程。
2、Authorization:是授权访问控制,用于对用户进行的操作进行人证授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
除了以上几个组件外,Shiro还有几个其他组件:
1、SessionManager :Shiro为任何应用提供了一个会话编程范式。
2、CacheManager :对Shiro的其他组件提供缓存支持。
2. Shrio认证
我们来看几个简单的demo
demo路径:
使用的maven测试的 maven需要的依赖:
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.41</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
</dependencies>
3.使用simpleAccountRealm 进行认证
package com.imooc.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
public class AuthenticationTest {
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void addUser() {
simpleAccountRealm.addAccount("Mark", "123456", "admin", "user");
}
@Test
public void testAuthentication() {
//1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
//2主题提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);//设置环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
System.out.println("isAuthenticated:" + subject.isAuthenticated());
//是否具备管理员角色
// subject.checkRole("admin");
subject.checkRoles("admin", "user");
//退出
// subject.logout();
// System.out.println("isAuthenticated:" + subject.isAuthenticated());
}
}
4.使用IniRealm 进行认证
package com.imooc.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class IniRealmTest {
IniRealm iniRealm = new IniRealm("classpath:user.ini");
@Test
public void testAuthentication() {
//1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
//2主题提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);//设置环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
System.out.println("isAuthenticated:" + subject.isAuthenticated());
//是否具备管理员角色
subject.checkRole("admin");
//是否具备用户删除的权限
subject.checkPermission("user:update");
//退出
}
}
user.ini:
[users]
Mark=123456,admin
[roles]
admin=user:delete,user:update
5.使用JdbcRealm进行认证
package com.imooc.test;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class JdbcRealmTest {
DruidDataSource dataSource = new DruidDataSource();
{
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
}
@Test
public void testAuthentication() {
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
//设置权限开关,默认为false
jdbcRealm.setPermissionsLookupEnabled(true);
String sql = "select password from test_user where user_name = ?";
jdbcRealm.setAuthenticationQuery(sql);
String rolesql = "select role_name from test_user_role where user_name = ?";
jdbcRealm.setUserRolesQuery(rolesql);
//1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
//2主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);//设置环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("xiaoming", "654321");
subject.login(token);
System.out.println("isAuthenticated:" + subject.isAuthenticated());
//是否具备管理员角色
// subject.checkRole("admin");
// subject.checkRoles("admin", "user");
//
// subject.checkPermission("user:select");
subject.checkRole("user");
}
}
使用JdbcRealm值得注意的是可以不设置
jdbcRealm.setAuthenticationQuery(sql)
和jdbcRealm.setAuthenticationQuery(sql);
这里就有问题我们不设置SQL也能认证呢,
我们看看JdbcRealm的源码发现:如果不设置SQL语句,他是有自己的查询语句的,不过我们要使用默认的sql需要数据库的表结构和字段与默认sql中的表字段同步
我的sql表结构字段如下:
users:
user_roles:
roles_permissions
6. 使用自定义Realm认证
使用自定义Realm需要继承AuthorizingRealm,并重写两个方法:
doGetAuthorizationInfo(授权)doGetAuthenticationInfo(认证)
package com.imooc.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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 java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class CustomRealm extends AuthorizingRealm {
//模拟数据库
Map<String, String> userMap = new HashMap<String, String>(16);
{
userMap.put("Mark", "e10adc3949ba59abbe56e057f20f883e");//原密码为:123456,因为使用了MD5
加密
super.setName("customRealm");
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户
String userName = (String) principalCollection.getPrimaryPrincipal();
//从数据库或者缓存中获取角色数据
Set<String> roles = getRolesByUserName(userName);
//从数据库或者缓存中获取权限数据
Set<String> permissions = getPermissionByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//设置权限
simpleAuthorizationInfo.setStringPermissions(permissions);
//设置角色
simpleAuthorizationInfo.setRoles(roles);
return simpleAuthorizationInfo;
}
private Set<String> getPermissionByUserName(String userName) {
Set<String> sets = new HashSet<String>();
sets.add("user:delete");
sets.add("user:add");
return sets;
}
private Set<String> getRolesByUserName(String userName) {
Set<String> sets = new HashSet<String>();
sets.add("admin");
sets.add("user");
return sets;
}
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 1. 从主体传过来的认证信息中,获得用户名
String userName = (String) authenticationToken.getPrincipal();
// 2. 通过用户名到数据库获取凭证
String password = getpasswordByUserName(userName);
if (null == password) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
//设置盐
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("zou"));
return authenticationInfo;
}
/*
* 模拟数据库查询凭证(获得密码)
* */
private String getpasswordByUserName(String userName) {
return userMap.get(userName);
}
}
CustomRealmTest测试:
package com.imooc.test;
import com.imooc.shiro.realm.CustomRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class CustomRealmTest {
@Test
public void testAuthentication() {
CustomRealm customRealm = new CustomRealm();
//1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customRealm);
// 2.声明CustomRealm使用了Md5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//加密算法为MD5
matcher.setHashAlgorithmName("md5");
//加密次数为1
matcher.setHashIterations(1);
customRealm.setCredentialsMatcher(matcher);
//2主题提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);//设置环境
Subject subject = SecurityUtils.getSubject();//获得主体
UsernamePasswordToken token = new UsernamePasswordToken("Mark", "123456");
subject.login(token);
System.out.println("isAuthenticated:" + subject.isAuthenticated());
//是否具备管理员角色
subject.checkRole("admin");
//是否具备用户删除的权限
subject.checkPermissions("user:add", "user:delete");
//退出
}
//用来计算123456加密后的密码为:e10adc3949ba59abbe56e057f20f883e
public static void main(String[] args) {
//盐为zou 一般为随机数,这里为了方便
Md5Hash md5Hash = new Md5Hash("123456", "zou");
System.out.println(md5Hash);
}
}