shiro基础
一. Shiro框架简介
shiro:是一个轻量级的安全框架。
Authentication:登录验证。
Authorization:授权。
SessionManagement:会话管理。(核心)
Cryptography:加密。
Web Support:web支持。
Caching:缓存支持。
Concurrency:多线程支持。
Testing:单元测试支持。
Run As:在允许的情况下,可以去模拟其他用户。
Remember Me:记住我。
二. Shiro框架代码流程:
Subject:使用者,一般指当前用户。
SecurityManager:安全管理者,全局安全管理,可以管理所有的用户。
Realm:相当于数据源。
SecurityUtils:安全工具类。
三. Shiro的适用范围:
四. 基础代码
pom.xml
中导入包
<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>
shiro.ini文件
[users]
zhangsan=123456,admin
lisi=123456,user,vip
[roles]
admin=delete,update,save,find
user=find
vip=find,update,save
java代码
// 操作ini文件
public class TestShiro {
@Test
public void test(){
// 得到核心Manager对象
SecurityManager securityManager = new
IniSecurityManagerFactory("classpath:shiro.ini").getInstance();
// 设置当前工具类中的securityManager
SecurityUtils.setSecurityManager(securityManager);
// 得到subject
Subject subject = SecurityUtils.getSubject();
// 得到(创建)当前登录用户信息
UsernamePasswordToken token = new UsernamePasswordToken("lisi", "123456");
// 当前登录中,shiro框架并不会因为登录不成功而返回null,而是直接抛出异常。
try {
// 登录
subject.login(token);
System.out.println("登录成功");
check();
}catch (Exception e){
System.out.println("登录失败");
e.printStackTrace();
}
}
public void check(){
Subject subject = SecurityUtils.getSubject();
System.out.println("当前用户为:" + subject.getPrincipal());
System.out.println("当前用户是否admin:" + subject.hasRole("admin"));
System.out.println("当前用户是否user:" + subject.hasRole("user"));
System.out.println("当前用户是否vip:" + subject.hasRole("vip"));
System.out.println("当前用户是否有权限delete:" + subject.isPermitted("delete"));
System.out.println("当前用户是否有权限update:" + subject.isPermitted("update"));
System.out.println("当前用户是否有权限save:" + subject.isPermitted("save"));
System.out.println("当前用户是否有权限find:" + subject.isPermitted("find"));
}
}
五. 整合ssm框架,使用shiro自带的数据库
pom.xml
导入包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
在web.xml
中添加shiro对应的过滤器配置,表示shiro权限验证需要拦截所有的请求
<!-- shiro filter -->
<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>
在spring.xml
中配置shiro框架
<!--shiro配置开始-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>
<!-- 数据库保存的密码是使用MD5算法加密的,所以这里需要配置一个密码匹配对象 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean>
<!-- 缓存管理 -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean>
<!--
使用Shiro自带的JdbcRealm类
指定密码匹配所需要用到的加密对象
指定存储用户、角色、权限许可的数据源及相关查询语句
-->
<bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="credentialsMatcher" ref="credentialsMatcher"></property>
<property name="permissionsLookupEnabled" value="true"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- Shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="jdbcRealm"></property>
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!--
Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行
Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- Shiro的核心安全接口,这个属性是必须的 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 要求登录时的链接(登录页面地址),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 -->
<property name="loginUrl" value="/login"></property>
<!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码) -->
<!-- <property name="successUrl" value="/" ></property> -->
<!-- 用户访问未对其授权的资源时,所显示的连接 -->
<property name="unauthorizedUrl" value="/"></property>
<property name="filterChainDefinitions">
<value>
/**=anon
</value>
</property>
</bean>
在spring-mvc.xml
中配置注解支持。
<!--使用shiro注解的配置-->
<aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
编写登录代码:
@Service
public class UserService {
// 登录
public void login(String username, String password)throws Exception{
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);
}
// 退出登录
public void logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
}
编写页面权限验证代码:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<base href="<%=request.getContextPath()%>/"/>
</head>
<body>
<a href="user/logout">退出登录</a>
登录成功,当前登录用户为:<shiro:principal></shiro:principal><br/>
当前用户角色为:
<shiro:hasRole name="admin">admin, </shiro:hasRole>
<shiro:hasRole name="user">user, </shiro:hasRole>
<shiro:hasRole name="cmanager">cmanager, </shiro:hasRole>
<shiro:hasRole name="mmanager">mmanager, </shiro:hasRole>
<shiro:hasRole name="xmanager">xmanager, </shiro:hasRole>
<ul>
M模块:
<shiro:hasPermission name="sys:m:find">
<li><a href="m/f">M模块查看</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:m:save">
<li><a href="">M模块添加</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:m:update">
<li><a href="">M模块修改</a></li>
</shiro:hasPermission>
<shiro:hasPermission name="sys:m:delete">
<li><a href="m/d">M模块删除</a></li>
</shiro:hasPermission>
</ul>
<ul>
C模块:
<li>
<shiro:hasPermission name="sys:c:find">
<a href="c/f">C模块查看</a>
</shiro:hasPermission>
</li>
<li>
<shiro:hasPermission name="sys:c:save">
<a href="">C模块添加</a>
</shiro:hasPermission>
</li>
<li>
<shiro:hasPermission name="sys:c:update">
<a href="">C模块修改</a>
</shiro:hasPermission>
</li>
<li>
<shiro:hasPermission name="sys:c:delete">
<a href="c/d">C模块删除</a>
</shiro:hasPermission>
</li>
</ul>
<ul>
X模块:
<li>
<shiro:hasPermission name="sys:x:find">
<a href="x/f">X模块查看</a>
</shiro:hasPermission>
</li>
<li>
<shiro:hasPermission name="sys:x:save">
<a href="">X模块添加</a>
</shiro:hasPermission>
</li>
<li>
<shiro:hasPermission name="sys:x:update">
<a href="">X模块修改</a>
</shiro:hasPermission>
</li>
<li>
<shiro:hasPermission name="sys:x:delete">
<a href="x/d">X模块删除</a>
</shiro:hasPermission>
</li>
</ul>
</body>
</html>
此时,页面上能够根据登录用户的不同,判断权限并显示不同的链接效果,但是我们发现仍旧可以通过url的方式去访问没有权限的路径。那么我们应该在对应的Controller中添加权限判断。
例如:
@Controller
@RequestMapping("/m")
public class MController {
@RequiresPermissions("sys:m:delete") // 判断是否拥有权限
@RequestMapping("/d")
public String delete(){
System.out.println("m-------d");
return "/success";
}
@RequiresPermissions("sys:m:find")
@RequestMapping("/f")
public String find(){
System.out.println("m--------f");
return "/success";
}
}
当不满足权限要求时,会抛出异常UnauthorizedException,建议使用全局异常处理来处理登录用户、角色、权限等异常。如下:
@ControllerAdvice
public class AllExceptionHandler {
@ExceptionHandler
public String doException(Exception e){
if (e instanceof UnauthorizedException){
return "/unauthorization";
}
return "/index";
}
}
六. 自定义数据库
根据用户、角色、权限关系的简单思考,得出用户与角色,角色与权限之间是多对多的关系,按照此关系设计出5张表。
用户表:users
用户角色表:user_role
角色表:roles
角色权限表:role_auth
权限表:auths
自定义一个Realm,继承AuthorizingRealm
public class MyRealm extends AuthorizingRealm {
@Resource
private AuthService authService;
// 表示与其他的realm区分
@Override
public String getName() {
return "myRealm";
}
// 角色和权限查询操作
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 得到当前唯一的用户
Users users = (Users) principals.iterator().next();
// 创建一个角色权限封装对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 得到所有的角色信息
List<String> roleCode = authService.findRoleCodeByUserId(users.getId());
info.setRoles(new HashSet<String>(roleCode));
// 得到所有的权限信息
List<String> authNames = authService.findAuthNameByUserId(users.getId());
info.setStringPermissions(new HashSet<String>(authNames));
return info;
}
// 登录操作
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 得到登录输入的用户名和密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
char[] chars = usernamePasswordToken.getPassword();
String password = new String(chars);
// 查询用户登录信息
Users users = authService.findByUsernameAndPwd(username, password);
// 将登录用户封装并返回(用户对象,密码,realm名称)
return new SimpleAuthenticationInfo(users, password, getName());
}
}
在spring.xml
中移除原JDBCRealm,使用自定义的realm
<bean id="myRealm" class="com.qianfeng.day22.util.MyRealm">
</bean>
<!-- Shiro安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"></property>
<property name="cacheManager" ref="cacheManager"></property>
</bean>