##一:什么是Shiro
认证和授权
1.官方首页: http://shiro.apache.org/
2.核心功能:
Authentication : 认证
Authorization : 授权
Session Management : 会话管理
Cryptography : 加密
3.代码调用流程:
application code : 自己实现的代码
subject : 主题/ 主体(人),代表的就是当前用户
securitymannager : 最核心的对象,所有功能的实现都由安全管理器实现
realm : 安全数据桥.DAO,所有的和用户相关的权限身份数据
##二:实现后台系统认证:
1.整合shiro框架:
1.1 导入坐标:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>${shiro.version}</version>
</dependency>
1.2 配置web.xml
<!--
Spring提供的,用于整合shiro框架的
一定要写在Struts框架的前端控制器之前
本filter创建的时候,需要注入一个对象,这个对象必须声明在applicationContext.xml中,
而且这个对象的id必须和本filter的name保持一致
-->
<filter>
<filter-name>shiroFilterFactoryBean</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilterFactoryBean</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.3 配置applicationContext.xml
<!--
工厂bean,初始化shiro框架提供的filter
-->
<bean id="shiroFilterFactoryBean" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入安全管理器 -->
<property name="securityManager" ref="securityManager"></property>
<!-- 登录页面的url,认证的页面 -->
<property name="loginUrl" value="/login.html"></property>
<!-- 认证成功以后跳转的页面 -->
<property name="successUrl" value="/index.html"></property>
<!-- 权限不足的时候,跳转的页面 -->
<property name="unauthorizedUrl" value="/unauthorized.html"></property>
<!-- 指定拦截规则 -->
<property name="filterChainDefinitions">
<!--
authc : 框架提供的一个拦截器,必须认证通过,认证通过就可以访问,认证失败,无法访问
anon : 框架提供的一个拦截器,可以匿名访问
拦截器执行的时候是从上往下执行的,一旦有一个匹配成功执行了,后面的规则不再会被执行
规则不可以折行 !!!!
-->
<value>
/css/* = anon
/data/* = anon
/images/* = anon
/js/** = anon
/validatecode.jsp* = anon
/** = authc
</value>
</property>
</bean>
<!-- 注册安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"></bean>
2 .用户登录安全校验,后台代码实现
2.1 UserController
@RequestMapping(value = "loginShiro",method = RequestMethod.POST)
public Map<String, Object> loginShiro(User user, HttpSession session) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("userStatus", 0);
// 当前用户
Subject subject = SecurityUtils.getSubject();
// 对用户输入的密码进行加密
user.setPassword(PWDEncryptUtil.PWDEncrypt("MD5", user.getPassword()));
System.out.println("传到userRealm的token带过去的password:"+user.getPassword());
// 创建令牌token
AuthenticationToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
System.out.println("传输过来的User:"+user.getUsername()+user.getPassword());
try {
// 执行登录
// 参数就传递给Realm中doGetAuthenticationInfo方法的参数
subject.login(token);
// 该方法的返回值是由Realm中创建SimpleAuthenticationInfo对象时,传入的第一个参数决定的
user = (User) subject.getPrincipal();
System.out.println("保存到session中的User:"+user);
// 把用户存入session
session.setAttribute("name", user.getName());
System.out.println("登陆的账户名称:"+user.getName());
map.put("name",user.getName());
map.put("userStatus", 1);
return map;//登录成功
} catch (UnknownAccountException e) {
System.out.println("账户不存在");
e.printStackTrace();
} catch (IncorrectCredentialsException e) {
System.out.println("密码错误");
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
System.out.println("其他异常:" + e.getMessage());
}
return map;//跳转到登录页面
}
2.2创建Realm
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
// 授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 需要根据当前的用户去查询对应的权限和角色
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
if ("admin".equals(user.getUserName())) {//所有角色和所有权限都赋予给admin
// 内置管理员(超级管理员)的权限和角色是在数据库写死的
// 角色其实是权限的集合,并不是所有的权限都会包含在某一个角色中
List<Role> roles = roleMapper.findAll();
for (Role role : roles) {
info.addRole(role.getRemark());//role.getKeyword
}
List<Permission> permissions = permissionMapper.findAll();
for (Permission permission : permissions) {
info.addStringPermission(permission.getKeyword());
}
} else {//根据当前用户获取角色和权限
List<Role> roles = roleMapper.findbyUid(user.getId());
for (Role role : roles) {
info.addRole(role.getRemark());//role.getKeyword
}
List<Permission> permissions = permissionMapper.findbyUid(user.getId());
for (Permission permission : permissions) {
info.addStringPermission(permission.getKeyword());
}
}
return info;
}
// 认证方法
// token就是Subject执行login方法传递进来的参数
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
// 根据用户名查找用户
User user = userMapper.findByUsername(username);
// 找到,比对密码
if (user != null) {
/**
*
* @param principal
* 当事人,主体.往往传递从数据库中查询出来的用户对象
* @param credentials
* 凭证,密码(是从数据库中查询出来的密码)
* @param realmName
*
*/
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
// 比对成功 处理后续逻辑
// 比对失败 抛异常
return info;
}
// 找不到 抛异常
return null;
}
}
2.3用户退出
@RequestMapping(value = "/logout",method = RequestMethod.GET)
public void logout(HttpServletRequest request, HttpServletResponse response,HttpSession session) throws IOException {
Subject subject = SecurityUtils.getSubject();
System.out.println(subject);
if (subject != null) {
try {
//注销
subject.logout();
//清空session
session.removeAttribute("name");
session.removeAttribute("user");
} catch (Exception ex) {
}
}
}
3.配置applicationContext.xml
<!-- 注册安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 注入realm -->
<property name="realm" ref="userRealm"></property>
</bean>
##三:动态授权方法
在applicationContext.xml中配置拦截规则
/hi.html = perms["courissser_pageQuery"]
在Realm中实现授权
// 授权方法
// 该方法会在用户每次请求需要权限校验的资源时调用
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("courier_pageQuery");
return info;
}
##四:注解方式实现权限控制
在对应的方法上添加注解
@RequiresPermissions("courier_delete")
配置applicationContext.xml
<!--
开启事务注解
JDK代理方式 : 根据目标对象所实现的接口,创建了一个代理对象
CGLib代理方式 : 创建一个目标对象的子类
proxy-target-class:true,使用cglib代理
proxy-target-class:false,使用jdk代理
-->
<tx:annotation-driven proxy-target-class="true"
transaction-manager="transactionManager" />
<!-- 基于Spring自动代理的方式,为Service层的对象创建代理对象 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<!-- 开启cglib代理 -->
<property name="proxyTargetClass" value="true"></property>
</bean>
<!--配置切面= 切点(向那里插入代码)+通知 (要插入的代码)-->
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"></property>
</bean>
##五 标签方式实现权限控制
页面必须是jsp页面,jsp页面引入shiro
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 认证的用户可以看到被标签包裹的内容 -->
<shiro:authenticated>
当前用户已经通过认证了
</shiro:authenticated>
<hr>
<!-- 检查用户是否拥有对应的权限,如果有就可以看到内容,没有就看不到 -->
<shiro:hasPermission name="courier_pageQuery">
你拥有aa权限
</shiro:hasPermission>
<hr>
<!-- 检查用户是否拥有对应的角色,如果有就可以看到内容,没有就看不到 -->
<shiro:hasRole name="admin">
你拥有admin权限
</shiro:hasRole>
</body>
</html>
##六 编码方式实现权限控制(不推荐)
subject.checkPermission("");// 检查权限
subject.checkRole("");// 检查角色
##七 Shiro权限控制方式总结
1.URL过滤器拦截实现权限控制
底层基于shiro框架提供的过滤器,由过滤器拦截客户请求,实现权限校验.体现在spring框架的配置文件中.
主要用于用户未登录时,保护系统资源
2.注解方式实现权限控制
底层基于cglib代理,为Service类创建代理对象,由代理对象实现权限校验,体现在Service类方法上.
主要用于用户登录后,保护系统资源
3.标签方式实现权限控制
底层基于标签技术,动态展示页面中的元素,体现在jsp页面上.
主要用于jsp页面,根据权限和角色保护对应的资源