1、概念
Apache Shiro是一个强大且易用的Java安全框架,有身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
- Spring security 重量级安全框架 A
- pache Shiro轻量级安全框架
2、四大基石
- 身份认证(登录) Authentication
- 授权(权限) Authorization
- 密码学 Cryptography
- 会话管理Session Management
除此还有
- Web Support: Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
- Caching:缓存是 Apache Shiro 中的第一层公民,来确保安全操作快速而又高效。
- Concurrency: Apache Shiro 利用它的并发特性来支持多线程应用程序。
- Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
- “Run As”:一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
- “Remember Me”:在会话中记住用户的身份,所以他们只需要在强制时候登录。
3、重要的对象
- Subject:当前用户
- SecurityManager:权限管理器(所有功能管理)
- Realm:获取权限数据
4、测试1—普通maven项目
建一个普通的maven项目
在官方的文件中找到一个samples
4.1、导包
<!--使用shiro需要先导包-->
<dependencies>
<!--shiro的核心包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</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.9</version>
</dependency>
</dependencies>
4.2、ini文件
在官方文档中找到ini文件,截取部分来进行测试。这些用户及权限,真实应该从数据库中获取,这里只是为了测试一些api。
# -----------------------------------------------------------------------------
#users 用户
#root 用户名,777777 密码,adming 角色
#guest 用户名,888888 密码,guest 角色
#
# -----------------------------------------------------------------------------
[users]
root = 777777, admin
guest = 888888, guest
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = employee:*
goodguy = winnebago:drive:eagle5
4.3、ShiroTest测试
public class ShiroTest {
@Test
public void testShiro() throws Exception {
//拿到权限管理工厂,数据源是配置文件内的shiro.ini
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//拿到权限管理器对象
SecurityManager securityManager = factory.getInstance();
//将权限管理器放入工具类中,这样所有的权限功能都能实现
SecurityUtils.setSecurityManager(securityManager);
//拿到当前用户(当前用户可能是一个游客,不确定是否登录)
Subject subject = SecurityUtils.getSubject();
//判断当前用户是否为登录用户,false表示未登录
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);
//未登录用户使用令牌登录,令牌为用户名和密码
if (!authenticated) {
System.out.println("是否登录:" + subject.isAuthenticated());
try {
UsernamePasswordToken root = new UsernamePasswordToken("root", "777777");
subject.login(root);
//未知账户异常
} catch (UnknownAccountException e) {
System.out.println("账户错误");
e.printStackTrace();
//密码错误,错误的凭证
} catch (AuthenticationException e) {
//最大的,登录异常
System.out.println("登录异常");
e.printStackTrace();
}
}
//判断是否有角色
System.out.println("是否是admin角色"+subject.hasRole("admin") );
//判断是否有权限
System.out.println("是否有employee:index权限"+subject.isPermitted("employee:index"));
System.out.println("是否有employee:save权限"+subject.isPermitted("employee:save"));
System.out.println("是否有employee:delete权限"+subject.isPermitted("employee:delete"));
System.out.println("是否有department:delete权限"+subject.isPermitted("department:delete"));
subject.logout();
System.out.println("是否登录:" + subject.isAuthenticated());
}
}
4.4、Myrealm
自定义realm,这里继承的是AuthorizingRealm这个类,覆写身份验证和权限验证
//自定义realm
public class MyRealm extends AuthorizingRealm {
//授权功能
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//创建一个授权对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//获取与设置角色
Set<String> roles = findRoles();
authorizationInfo.setRoles(roles);
//拿到与设置权限
Set<String> perms = findPerms();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
//查询角色
private Set<String> findRoles() {
Set<String> roles = new HashSet<String>();
roles.add("admin");
return roles;
}
//查询权限
private Set<String> findPerms() {
Set<String> perms = new HashSet<String>();
perms.add("*");
return perms;
}
//登录功能
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//将令牌转换成用户密码令牌
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
//获取到用户
String username = usernamePasswordToken.getUsername();
//根据用户名查询密码,如果返回的为空,说明用户名不存在
String password = findByUsernae(username);
//判断用户是否存在
if (password == null) {
return null;
}
//如果用户存在,判断密码是否正确,需要两个密码,一是前台传来的,二是在后台查到的密码
/**
* 第一个参数:principal(主体) =》 登录成功后在任何地方都可以拿到的对象
* 以前开始登录成功我们就把用户放到session中
* 第二个参数:密码(从数据库查出来的密码)
* 第三个参数:加盐
* 第四个参数:当前realm的名称(随便取)
*/
ByteSource byteSource = ByteSource.Util.bytes("1111");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password,byteSource,getName());
return authenticationInfo;
}
//模拟数据库查询
public String findByUsernae(String username) {
if ("admin".equals(username)) {
return "dfd767e97113b55283c9568c9ef1ab8a";
} else if ("root".equals(username)) {
return "777777";
}
return null;
}
}
4.5、Myrealm测试
public class MyRealmTest {
@Test
public void testMyRealm() throws Exception {
//创建权限管理
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//创建一个realm
MyRealm myRealm = new MyRealm();
//设置凭证匹配器,HashedCredentialsMatcher
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//匹配器算法名字
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置迭代次数
hashedCredentialsMatcher.setHashIterations(10);
//加盐的方式在密码验证
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
//把realm放入权限管理器中
securityManager.setRealm(myRealm);
//将权限管理器放入工具中,功能都可以实现
SecurityUtils.setSecurityManager(securityManager);
//拿到当前用户
Subject subject = SecurityUtils.getSubject();
//验证用户是否为登录
System.out.println("是否为登录" + subject.isAuthenticated());
//没有登录就去登录
if (!subject.isAuthenticated()) {
//创建登录的令牌
try {
AuthenticationToken token = new UsernamePasswordToken("admin", "123456");
subject.login(token);
//用户名不存在
} catch (UnknownAccountException e) {
System.out.println("用户名不存在");
e.printStackTrace();
//密码错误
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
e.printStackTrace();
} catch (AuthenticationException e) {
System.out.println("高级错误");
e.printStackTrace();
}
}
System.out.println("是否为admin角色" + subject.hasRole("admin"));
System.out.println("是否有employee:xxxx的权限" + subject.isPermitted("employee:xxxx"));
System.out.println("是否为登录" + subject.isAuthenticated());
}
/*
* 密码加密算法
* MD5,SHA
* algorithmName:算法名称
* source:密码
* salt:加盐
* hashlterations:10次
* */
@Test
public void testHashPassword() throws Exception {
SimpleHash simpleHash2 = new SimpleHash("MD5", "123456");
SimpleHash simpleHash = new SimpleHash("MD5","123456","1111",10);
System.out.println(simpleHash.toHex());
}
}
5、测试2----mavenweb项目/集成Spring
5.1、导包
<!-- shiro(权限框架)的支持包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
<type>pom</type>
</dependency>
<!-- shiro与Spring的集成包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
5.2、web.xml中配置代理过滤器
<!--shiro过滤器
Delegating(授权)Filter(过滤器)Proxy(代理)
只拦截其他不做
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.3、自定义realm(暂时不从数据库中获取)
//自定义realm
public class AisellRealm extends AuthorizingRealm {
//授权功能
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//创建一个授权对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//获取与设置角色
Set<String> roles = findRoles();
authorizationInfo.setRoles(roles);
//拿到与设置权限
Set<String> perms = findPerms();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
//查询角色
private Set<String> findRoles() {
Set<String> roles = new HashSet<String>();
roles.add("admin");
return roles;
}
//查询权限
private Set<String> findPerms() {
Set<String> perms = new HashSet<String>();
perms.add("employee:*");
return perms;
}
//登录功能
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//将令牌转换成用户密码令牌
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
//获取到用户
String username = usernamePasswordToken.getUsername();
//根据用户名查询密码,如果返回的为空,说明用户名不存在
String password = findByUsernae(username);
//判断用户是否存在
if (password == null) {
return null;
}
//如果用户存在,判断密码是否正确,需要两个密码,一是前台传来的,二是在后台查到的密码
/**
* 第一个参数:principal(主体) =》 登录成功后在任何地方都可以拿到的对象
* 以前开始登录成功我们就把用户放到session中
* 第二个参数:密码(从数据库查出来的密码)
* 第三个参数:加盐
* 第四个参数:当前realm的名称(随便取)
*/
ByteSource byteSource = ByteSource.Util.bytes("1111");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password,byteSource,getName());
return authenticationInfo;
}
//模拟数据库查询
public String findByUsernae(String username) {
if ("admin".equals(username)) {
return "dfd767e97113b55283c9568c9ef1ab8a";
} else if ("root".equals(username)) {
return "777777";
}
return null;
}
}
5.3、准备工厂返回权限FilterChainDefinitionMapFactory
这个工厂,主要是解决在applicationContext-shiro.xml中配置权限的局限性,将权限写在java代码中,方面修改。
- 返回的Map值是有顺序的
- 修改后要重启(热启动无效)
package com.xuxusheng.aisell.web.shiro;
import java.util.LinkedHashMap;
import java.util.Map;
public class FilterChainDefinitionMapFactory {
/*
*
* /login = anon
/s/permission.jsp = perms[user:index]
/** = authc
* */
public Map<String, String> createMap() {
Map<String, String> map = new LinkedHashMap<>();
//一定注意顺序
//不拦截的
map.put("/login","anon");
//设置权限
map.put("/employee/index","perms[employee:index]");
map.put("/department/index","perms[department:index]");
//全部拦截
map.put("/** ","authc");
return map;
}
}
5.4、applicationContext-shiro.xml的配置
注意:先在 applicationContext.xml 中引入它
< import resource=“classpath:applicationContext-shiro.xml” />
<?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-3.0.xsd">
<!--
Shiro的核心对象(权限管理器)
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jpaRealm)
-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="aisellRealm"/>
</bean>
<!--
JpaRealm jpaRealm = new JpaRealm();
配置咱们的自定义realm
-->
<bean id="aisellRealm" class="com.xuxusheng.aisell.web.shiro.AisellRealm">
<!--Realm的名称-->
<property name="name" value="aisellRealm"/>
<property name="credentialsMatcher">
<!-- 配置哈希密码匹配器 -->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--加密方式:MD5-->
<property name="hashAlgorithmName" value="MD5"/>
<!--迭代次数-->
<property name="hashIterations" value="10" />
</bean>
</property>
</bean>
<!-- 这三个配置好,可以支持注解权限 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--
shiro真正的过滤器(功能就是它完成的)
这个bean的名称必需和web.xml里的的代理过滤器名字相同
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--必需要用到权限管理器-->
<property name="securityManager" ref="securityManager"/>
<!--如果你没有登录,你会进入这个页面-->
<property name="loginUrl" value="/s/login.jsp"/>
<!--登录成功后,进入的页面(一般没什么用)-->
<property name="successUrl" value="/s/main.jsp"/>
<!--如果你没有权限,你会进入这个页面-->
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
<!-- 过滤描述
anon:不需要登录也可以访问
authc:登录与权限的拦截
perms:如果你有user:index的权限,你就可以访问:/s/permission.jsp
-->
<!--
<property name="filterChainDefinitions">
<value>
/login = anon
/s/permission.jsp = perms[user:index]
/** = authc
</value>
</property>
-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" />
</bean>
<!--
以前在四个创建Bean的方法中讲过
ShiroFilterMapFactory shiroFilterMapFactory = new shiroFilterMapFactory();
Map filterChainDefinitionMap=shiroFilterMapFactory.createMap();
-->
<!--拿到shiroFilterMapFactory里面的createMap方法的值 -->
<bean id="filterChainDefinitionMap" factory-bean="shiroFilterMapFactory" factory-method="createMap" />
<!--配置返回shiro权限拦截的bean-->
<bean id="shiroFilterMapFactory" class="com.xuxusheng.aisell.web.shiro.FilterChainDefinitionMapFactory"/>
</beans>
更多详细的介绍参考链接
【让 Apache Shiro 保护你的应用】https://www.infoq.cn/article/apache-shiro/?itm_source=infoq_en&itm_medium=link_on_en_item&itm_campaign=item_in_other_langs