目录
认识Shiro的整体架构,各组件的概念
简介
- Apache 的强大灵活的开源安全框架;
- 认证、授权、企业会话管理、安全加密;
Shiro与Spring Security比较
Apache Shiro | Spring Security |
---|---|
简单灵活 | 复杂笨重 |
可脱离Spring | 不可脱离Spring |
粒度较粗 | 粒度更细 |
Shiro整体架构
Shiro通过Security Manager提供安全服务
Security Manager管理着其他组件的实例
- Authenticator(认证器):管理登录,登出
- Authorizer(授权器):赋予主体权限
- Session Manager(Session管理器):Shiro自己实现的管理机制,不借用任何容器使用Session
- Session Dao(提供Session的操作):主要有:增删改查。
- Cache Manager(缓存管理器):角色和权限数据缓存。
- Pauggable Realms(数据库与数据源的桥梁):shiro获取数据是通过realms来获取的。
流程:
- 主体提交请求到Security Manager.
- Security Manager调用Authenticator进行认证。(Authenticator认证获取数据是通过realms获取的,再从数据源中获取信息)数据源信息和主体提交的信息比对。
- (Authorizer授权获取数据是通过realms获取的,再从数据源中获取数据)数据源信息和主体提交的信息做比对。
- 数据加密
Shiro认证,授权的过程
shiro认证
Shiro安全认证简单流程如图:
- 构建SecurityManager环境
- 主体提交认证请求
- SecurityManager认证
- Authenticator认证
- Realm验证(用户名,密码信息)
注:3,4,5步认证在主体提交认证里面
package org.example;
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 {
public SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void addUser(){
simpleAccountRealm.addAccount("mark", "123456");
}
@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());
}
}
Shiro授权
Shiro授权
与之前Shiro认证的步骤一样。
只不过在Realm 的SimpleAccountRealm中可以添加addUser的时候,可以添加入多个角色(即可变数组的形式)
同样进行授权验证即检验该登录用户是否具备该角色的时候,使用:
subject.checkRoles(可变参数)的形式检验。
必须在登录的情况下,其他步骤与认证相同。
package org.example;
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 {
public 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.checkRoles("admin", "user");
}
}
Shiro自定义的Realm,Filter
IniRealm配置文件
IniRealm就是把 realm 的信息以.ini的配置文件形式保存,
其他的和认证授权没啥区别
package org.example;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class IniRealmTest {
@Test
public void testAuthentication(){
//获取realm配置文件
IniRealm iniRealm = new IniRealm("classpath:user.ini");
//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.checkRoles("admin", "user");
}
}
ini配置文件
[users]
mark=123456,admin,user
[roles]
admin=user:delete,user:update
JdbcRealm
JdbcRealm就是把 realm 的信息从数据库里去查找,默认有一些JdbcRealm自带有一些默认的SQL。realm的信息主要有用户信息users,角色信息user_roles和角色的权限信息roles_permissions
验证信息的SQL也可以自定义编写
注意:权限校验要在jdbcRealm里开启权限
package org.example;
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.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
public class JdbcRealmTest {
DruidDataSource dataSource = new DruidDataSource();
{
dataSource.setUrl("jdbc:mysql://localhost:3306/shiro_demo?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true");
dataSource.setUsername("root");
dataSource.setPassword("root");
}
@Test
public void testAuthentication(){
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true);
//自定义用户信息验证SQL
String userSql = "select password from users_test where username= ?";
jdbcRealm.setAuthenticationQuery(userSql);
//自定义角色验证SQL
String roleSql = "select role_name from user_roles_test where username = ?";
jdbcRealm.setUserRolesQuery(roleSql);
//自定义角色权限验证SQL
String permissionSql = "select permission from roles_permissions_test where role_name =?";
jdbcRealm.setPermissionsQuery(permissionSql);
//1.构建SecurityManager环境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
//2. 主体提交认证请求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
// UsernamePasswordToken token = new UsernamePasswordToken("mark", "123456");
UsernamePasswordToken token = new UsernamePasswordToken("rose", "111222");
subject.login(token);
System.out.println("isAuthenticated:" + subject.isAuthenticated());
// subject.checkRoles("admin");
subject.checkRoles("user");
// subject.checkPermission("user:select");
subject.checkPermission("user:update");
}
}
自定义Realm
自定义的realm,要注意以下几个步骤:
- 首先继承并实现类AuthorizingRealm的方法。其中方法doGetAuthenticationInfo主要做认证操作,即可以通过用户名,查询出相应的密码,然后将用户名与密码一同返回,shiro会自动根据传入的用户名和密码与此realm返回的用户名和密码做比对,返回你想要的结果。
同理,doGetAuthorizationInfo主要是用来做角色和权限的验证,也是通过用户名,从数据库中查找到相应的角色或者权限的数据并返回一个simple的授权类,授权系统会根据传入的参数与返回的结果集对比,存在返回TRUE,不存在则抛出异常。 - 两者方法只能获取用户名称,通过用户名称连接数据库获取其他信息。这里只是做数据准备操作,并不做判断是否传入的值与其相符的操作,此操作在此之后进行
- 根据两者的返回对象分别返回simple类型的对象。认证的对象需要将你获取的用户名和密码放到改造方法中。授权对象需要set到相应的方法中。
package org.example.config;
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.AuthenticatingRealm;
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>();
{
usermap.put("mark", "123456");
super.setName("customRealm");
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.getPrimaryPrincipal();
//从数据库缓存中获取角色数据
Set<String> roles = getRolesByUserName(username);
//获取用户权限
Set<String> permissions = getPermissionsByUserName(username);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
private Set<String> getPermissionsByUserName(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 = getPasswordUsername(userName);
if (password == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo("Mark", password, "customRealm");
return authenticationInfo;
}
//模拟数据库查询凭证
public String getPasswordUsername(String username){
return usermap.get(username);
}
}
shiro加密
Shiro散列配置
-
HashedCredentialsMatcher
通过HashedCredentialsMaster的方式加密,创建对象,并设置加密次数以及加密名称,将此加密对象设置到Realm的setCredentialsmaster的方法中,即该Realm采用此密码加密的方式。
后台的密码采用MD5的hash码值来存储。
-
盐的使用
加盐的意思就是在密码中拼接其他字符串,组成一个新的密码,然后对这个密码进行md5加密。同时,可以在自定义realm里面的doGetAuthenticationInfo方法进行设置盐进行解密
Shiro Session管理
Shiro 缓存管理
Shiro集成Spring
shiro集成spring
1,引入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.8.0</version>
</dependency>
2,新建一个xml文件配置shiro主体
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="login.html"/>
<property name="unauthorizedUrl" value="403.html" />
<property name="filterChainDefinitions" >
<value>
/login.html = anon
/subLogin = anon
/* = authc
</value>
</property>
</bean>
<!-- 创建SecurityManager对象-->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="realm" />
</bean>
<bean id="realm" class="org.example.shiro.realm.CustomRealm" >
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--设置加密的算法-->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"
id="credentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1"/>
</bean>
</beans>
通过注解的方式配置角色权限
- 开启aop设置TRUE;
- 添加LifecycleBeanPostProcessor;
- 添加AuthorizationAttributeSourceAdvisor
<aop:config proxy-target-class="true" />
<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
- 接口上面配置注解
@RequestMapping(value = "/test", method = RequestMethod.GET)
@ResponseBody
@RequiresRoles("admin")
public String testRoles() {
return "access";
}
@RequestMapping(value = "/test", method = RequestMethod.GET)
@ResponseBody
@RequiresPermissions("add")
public String testPermissions() {
return "access";
}
shiro过滤器
shiro内置过滤器
角色相关:
- anon 代表无需权限
- authBasic
- authc 代表需要认证才能访问
- user 代表需要存在用户对象才能访问
- logout 登录退出才能访问
权限相关:
- perms 拥有权限才能被访问
- roles 拥有角色才能被访问
- ssl
- port 相应端口号才能访问
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="login.html"/>
<property name="unauthorizedUrl" value="403.html" />
<property name="filterChainDefinitions" >
<value>
/login.html = anon
/subLogin = anon
/testRole = roles["admin"]
/testRole1 = roles["admin","admin1"]
/testPerms = perms["user:delete"]
/testPerms1 = perms["user:delete","user:update"]
/* = authc
</value>
</property>