Shiro初步学习-01
1、Shiro简介
用户权限管理一般是对用户页面、按钮的访问权限管理。Shiro 框架是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码和会话管理,对于 Shiro 的介绍这里就不多说。本篇博客主要是了解 Shiro 的基础使用方法,在权限管理系统中集成 Shiro 实现登录、url 和页面按钮的访问控制。
2、主要功能
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade(外观)模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
3、shiro与spring security比较
shiro可以脱离spring,比较灵活,上手快
spring security必须继承spring使用,笨重,上手困难
4、shiro整体框架图
①上面标记为1的是shiro的主体部分subject,可以理解为当前的操作用户
② Security Manager为Shiro的核心,shiro是通过security Manager来提供安全服务的,security Manager管理着Session Manager、Cache Manager等其他组件的实例:Authenticator(认证器,管理我们的登录登出) Authorizer(授权器,负责赋予主体subject有哪些权限) Session Manager(shiro自己实现的一套session管理机制,可以不借助任何web容器的情况下使用session) Session Dao(提供了session的增删改查操作) cache Manager(缓存管理器,用于缓存角色数据和权限数据) Pluggable Realms(shiro与数据库/数据源之间的桥梁,shiro获取认证信息、权限数据、角色数据都是通过Realms来获取)
③ 上图标记为2的cryptography是用来做加密的,使用它可以非常方便快捷的进行数据加密。
④ 上面箭头的流程可以这样理解:主体提交请求到Security Manager,然后由Security Manager调用Authenticator去做认证,而Authenticator去获取认证数据的时候是通过Realms从数据源中来获取的,然后把从数据源中拿到的认证信息与主体提交过来的认证信息做比对。授权器Authorizer也是一样。
5、shiro认证
① 创建Security Manager:Security Manager是用来提供安全服务的,所以在做shiro认证的时候要先创建此对象
② 主体Subject提交请求给Security Manager
③ Security Manager调用Authenticator组件做认证
④Authenticator通过Realm来从数据源中获取认证数据
6、代码示例
6.1、shiro认证
/**
* 测试认证
*/
public class AuthenticationTest {
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
JDBCRealm jdbcRealm = new JDBCRealm();
//再认证之前先在realm中添加一个用户信息,创建Security Manager的时候需要
@Before
public void addUser() {
simpleAccountRealm.addAccount("lfq", "123456");
}
@Test
public void testAutentication() {
//构建Security Manager
//Security Manager是用来提供安全服务的,所以在做shiro认证的时候要先创建此对象,创建Security Manager对象之后要设置Realm)
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
//获取Security Manager提交的subject请求,而主体subject可以通过shiro提供的一个工具类SecurityUtils来获取
//使用SecurityUtils之前要设置Security Manager环境
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
//主体Subject提交请求给Security Manager --> subject.login(token);
//提交请求时需要一个token,所以要先创建token
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("lfq", "1234567");
try {
subject.login(usernamePasswordToken);
} catch (AuthenticationException e) {
//e.printStackTrace();
System.out.println("login fail!");
}
// shiro提供了一个检查主体subject是否认证的方法isAuthenticated(),此方法的返回结果是一个boolean值
if (subject.isAuthenticated()) {
System.out.println("login success!");
}
subject.logout();
System.out.println(subject.isAuthenticated());
}
}
6.2、shiro授权
/**
* 测试授权
*/
public class AuthorizerTest {
//...
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void addUser() {
simpleAccountRealm.addAccount("lfq", "123456");
}
@Test
public void testAuthorizer() {
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("lfq", "123456");
try {
subject.login(usernamePasswordToken);
} catch (AuthenticationException e) {
//e.printStackTrace();
System.out.println("login fail!");
}
if (subject.isAuthenticated()) {
//检查角色
subject.checkRoles("admin");
}
}
}
7、自定义realm
在说自定义Realm之前先讲一下Shiro的内置Realm:
7.1、IniRealm
在项目中新建一个admin.ini文件,下面的内容的意思是,新建了一个用户,账号是xiehuaxin,密码为123456,此账号的角色为admin,而admin角色又具有删除用户和更新用户的权限(多个权限中间用逗号分隔),注意这里的“user:delete”不是一定要这样写的,只是一个字符串,你也可以写成"admin=shanchuyonghu",这要在校验的时候也用“shanchuyonghu”来匹配就行了,当然这种命名习惯还是不建议的,建议使用“user:delete”这种对象+动作的命名方式。
[users]
lfq=123456,admin
[roles]
admin=user:delete,user:update,user:add
/**
* 测试realm
*/
public class IniRealmTest {
@Test
public void testAutentication(){
IniRealm iniRealm = new IniRealm("classpath:admin.ini");
//构建Manager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("lfq","123456");
subject.login(usernamePasswordToken);
if (subject.isAuthenticated()) {
//赋权限
subject.checkRoles("admin");
subject.checkPermission("user:add");
}
}
}
7.2、JDBCRelam
<!-- 引入驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<!--引入数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.4</version>
</dependency>
代码演示:
/**
* jdbcRealm
*/
public class JDBARealmTest {
DruidDataSource druidDataSource = new DruidDataSource();
{
druidDataSource.setUrl("jdbc:mysql://localhost:3306/study");
druidDataSource.setUsername("root");
druidDataSource.setPassword("123456");
}
@Test
public void testJDBCRealm() {
//创建对象
JdbcRealm jdbcRealm = new JdbcRealm();
//设置数据源
jdbcRealm.setDataSource(druidDataSource);
//设置权限开关,只有设置为true,才回去查询权限语句
jdbcRealm.setPermissionsLookupEnabled(true);
//登录认证sql
String passSql = "select password from user where name=?";
jdbcRealm.setAuthenticationQuery(passSql);
//查询权限sql
String ruleSql = "select rule from rule where userid=?";
jdbcRealm.setUserRolesQuery(ruleSql);
//...
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("lfq", "666");
subject.login(usernamePasswordToken);
if (subject.isAuthenticated()) {
System.out.println("login success!");
}
}
}
7.3、自定义Relam
package com.shiro.demo.shiroTest;
import org.apache.shiro.SecurityUtils;
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.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.junit.Test;
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>();
{
userMap.put("lfq", "666");
}
/**
* 用来做授权(就是checkRole,checkPermission时用到的)
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户名
String userName = (String) principalCollection.getPrimaryPrincipal();
//从数据库或者缓存中获取用户角色信息
Set<String> rolesByUserName = getRolesByUserName(userName);
//权限数据
Set<String> permissionsByUserName = getPermissionsByUserName(userName);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(rolesByUserName);
simpleAuthorizationInfo.setStringPermissions(permissionsByUserName);
return simpleAuthorizationInfo;
}
/**
* 用来做认证(就是login时用到的)
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//先通过主题传过看来的数据获取用户名
String userName = (String) authenticationToken.getPrincipal();
String password = getPasswordByUserName(userName);
if (password == null) return null;
//查询到用户则返回该对象
SimpleAuthenticationInfo simpleAuthorizationInfo = new SimpleAuthenticationInfo(userName, password, "custorRealm");
//如果密码加密处理的时候加盐,也要将其返回,不能写死
//simpleAuthorizationInfo.setCredentialsSalt(ByteSource.Util.bytes("xie"));
return simpleAuthorizationInfo;
}
private String getPasswordByUserName(String userName) {
//这里我就不写查询数据库了,就模拟去查数据库
return userMap.get(userName);
}
/**
* 通过用户名获取到角色数据(这里为了简单点就不真的去查询数据库了,仅模拟数据库查询,代表有两个角色)
*
* @param userName
* @return
*/
private Set<String> getRolesByUserName(String userName) {
Set<String> sets = new HashSet<String>();
sets.add("admin");
sets.add("user");
return sets;
}
/**
* 从数据库或缓存中获取权限数据(这里模拟数据库查询)
*
* @param userName
* @return
*/
private Set<String> getPermissionsByUserName(String userName) {
Set<String> sets = new HashSet<String>();
sets.add("user:delete");
sets.add("user:add");
return sets;
}
/**
* 测试自定义realm
*/
@Test
public void testCustomRealm() {
CustomRealm customRealm = new CustomRealm();
//加密
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//设置加密算法
hashedCredentialsMatcher.setHashIterations(1);//设置加密次数
customRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//...
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("lfq", "666");
subject.login(usernamePasswordToken);
if (subject.isAuthenticated()) {
subject.checkRole("admin");
subject.checkPermission("user:add");
}
}
}