一、Shiro角色管理
1.1 模拟数据
角色:不同意味着不同权限
示例代码:
在main.html中添加三个超链接,分别访问三个不同的HTML页面
<body>
<p>用户的后台管理页面</p>
<a href="/admin.html">管理员能访问的页面</a><br>
<a href="/teacher.html">teacher能访问的</a><br>
<a href="/student.html">student能访问的</a><br>
<button id="logout">注销</button>
</body>
在static下创建这三个页面
1.1.1 shiro配置类
在过滤器方法中添加角色配置
注意:roles后[]中的角色信息是程序员自定义的,其中,OrFilter需要程序员手写,并且需要配置
//role 角色,当请求相关url时,匹配到的过滤器用roles表示
//访问该URL必须是[]中指定的角色
//[]中的名字是程序员自定义的
//当shiro识别到roles过滤器时会自动调用realm中获取授权信息的方法
map.put("/admin.html","roles[admin]");
map.put("/teacher.html","roles[teacher]");
map.put("/student.html","roles[student]");
//必须同时为admin和teacher才可访问
map.put("/adminAndTeacher.html","roles[admin,teacher]");
//自定义过滤器OrFilter:表示只要具备其中一个角色都可以访问
map.put("/adminOrTeacher.html","or[admin,teacher]");
创建过滤器bean
//4.创建自定义过滤器bean
@Bean
public OrFilter orFilter(){
return new OrFilter();
}
将过滤器注册到过滤器factoryBean中
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,OrFilter orFilter){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//5.注册自定义的过滤器
//javax.servlet.Filter
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("or",orFilter);
factoryBean.setFilters(filterMap);
//............此处代码省略.................
return factoryBean;
}
1.1.2 OrFilter过滤器
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class OrFilter extends AuthorizationFilter {
//返回值为true表示能够访问,返回值为false表示不允许访问
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
//1.获取Subject
Subject currentUser = SecurityUtils.getSubject();
//2.获取到过滤器中设置的角色信息
String[] roles = (String[]) mappedValue;
//3.判断
if(roles!=null && roles.length>0){
for (String role : roles) {
if (currentUser.hasRole(role)){
return true;
}
}
}
return false;
}
}
1.1.3 Realm类
在realm获取授权信息方法中模拟从数据库中获取到角色信息,并封装返回
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("获取授权信息(角色、权限)");
//获取账号信息
String account = (String) principals.getPrimaryPrincipal();
//模拟从数据库中获取到了当前用户的角色信息
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("teacher");
roles.add("student");
//封装角色信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//
return info;//返回给securityManger(subject)
}
1.2 数据库中获取数据
创建表格
CREATE TABLE role(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(20)
);
CREATE TABLE user_role(
uid INT,
rid INT
);
1.2.1 Role实体类
@Data
public class Role {
private int id;
private String name;
}
1.2.2 修改User实体类
@Data
@Accessors(chain = true)
public class User {
private int id;
private String account;
private String pwd;
private List<Role> roles;
}
1.2.3 RoleMapper
@Mapper
@Repository
public interface RoleMapper {
//通过用户id去role、user_role做连表查询
@Select("select * from user_role ur,role r where ur.rid=r.id and ur.uid = #{uid}")
public List<Role> findRolesByUid(int uid);
}
1.2.4 UserMapper
//通过账号查询用户的信息及角色信息
@Select("select * from user where account = #{account}")
@Results({
@Result(id = true,column = "id",property = "id"),
@Result(column = "id",property = "roles"
,many = @Many(select = "com.woniuxy.shiro.mapper.RoleMapper.findRolesByUid"))
})
public User findUserAndRolesByAccount(String account);
1.2.5 修改Realm类
private static final Logger logger = LoggerFactory.getLogger(CustomRealm.class);
/*
* 获取权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("获取授权的信息(角色、权限)");
//获取账号信息
String account = (String) principals.getPrimaryPrincipal();
//以账号作为查询条件,查询出当前用户的所有角色 权限管理系统中一般都存在一个账号多个角色的情况
User user = userService.findUserAndRolesByAccount(account);
logger.info(user.toString());
Set<String> roles = new HashSet<>();
//遍历获取角色信息
for (Role role : user.getRoles()) {
roles.add(role.getName());
}
//封装角色信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//返回给securityManager(subject)
return info;
}
二、Shiro权限管理
2.1 模拟数据
2.1.1 shiro配置类
在过滤器方法中添加权限配置
//权限配置 permissions:权限
map.put("/teacher/add","perms[teacher:add]");
2.1.2 TeacherController
@RestController
@RequestMapping("/teacher")
public class TeacherController extends ExceptionCenter{
private static final Logger logger = LoggerFactory.getLogger(TeacherController.class);
@RequestMapping("/add")
public String add(){
logger.info("添加教师信息");
return "success";
}
}
2.1.3 修改Realm类
修改realm获取授权信息的方法,模拟从数据库中获取权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("获取授权信息(角色、权限)");
//获取账号信息
String account = (String) principals.getPrimaryPrincipal();
//以账号作为查询条件查询出当前用户的所有角色 权限管理系统一般都存在一个账号多个角色情况
User user = userService.findUserAndRolesByAccount(account);
//模拟从数据库中获取到了当前用户的角色信息
Set<String> roles = new HashSet<>();
//遍历获取角色、权限信息
for (Role role : user.getRoles()) {
roles.add(role.getName());
}
//假设从数据库中获取到了权限信息
Set<String> perms = new HashSet<>();
//perms.add("teacher:add");
perms.add("teacher:del");
perms.add("teacher:update");
perms.add("teacher:find");
//封装角色信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//封装权限信息
info.setStringPermissions(perms);
//
return info;//返回给securityManger(subject)
}
2.2 数据库中获取数据
2.2.1 Perm实体类
@Data
public class Perm {
private int id;
private String name;
}
2.2.2 修改Role实体类
@Data
public class Role {
private int id;
private String name;
private List<Perm> perms;
}
2.2.3 PermMapper
@Mapper
@Repository
public interface PermMapper {
//根据rid联表查询
@Select("select * from role_perms rp,perms p where rp.pid = p.id and rp.rid=#{rid}")
public List<Perm> findPermsByRid(int rid);
}
2.2.4 修改RoleMapper
@Mapper
@Repository
public interface RoleMapper {
//通过用户id去role、user_role联表查询
@Select("select * from user_role ur,role r where ur.rid = r.id and ur.uid = #{uid}")
@Results({
@Result(id = true,column = "rid",property = "id"),
@Result(column = "rid",property = "perms"
,many = @Many(select = "com.woniuxy.shiro.mapper.PermMapper.findPermsByRid"))
})
public List<Role> findRolesByUid(int uid);
}
2.2.5 修改Realm类
修改realm获取授权信息方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("获取授权的信息(角色、权限)");
//获取账号信息
String account = (String) principals.getPrimaryPrincipal();
//以账号作为查询条件,查询出当前用户的所有角色 权限管理系统中一般都存在一个账号多个角色的情况
User user = userService.findUserAndRolesByAccount(account);
logger.info(user.toString());
Set<String> roles = new HashSet<>();
Set<String> perms = new HashSet<>();
//遍历获取角色、权限信息
for (Role role : user.getRoles()) {
roles.add(role.getName());
for (Perm perm : role.getPerms()) {
perms.add(perm.getName());
}
}
//封装角色信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//封装权限信息
info.setStringPermissions(perms);
//返回给securityManager(subject)
return info;
}
三、注解开发
shiro支持注解方式进行开发,但是没有提供在主启动类上使用注解开启shiro的注解扫描,需要自己在shiro配置类中配置
3.1 shiro配置类
//6.配置shiro注解的支持:shiro注解基于AOP实现
//6.1 配置通知
@Bean
public AuthorizationAttributeSourceAdvisor advisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor sourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
sourceAdvisor.setSecurityManager(securityManager);
return sourceAdvisor;
}
//6.2 配置代理器
@Bean
public DefaultAdvisorAutoProxyCreator creator(){
DefaultAdvisorAutoProxyCreator proxyCreator =
new DefaultAdvisorAutoProxyCreator();
//开启目标代理
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
3.2 TeacherController中添加方法测试
//or或者关系,and并且关系
//设置角色权限
@RequiresRoles(value = {"admin","teacher"},logical = Logical.OR)
//设置权限
// @RequiresPermissions({"teacher:del"})
@RequestMapping("/del")
public String del(){
logger.info("删除教师信息");
return "success";
}
运行程序,访问url,报500错误,表明注解生效,但是shiro没有提供处理注解报错的方法,需要自己通过异常处理解析器进行处理
3.3 异常解析器父类
public class ExceptionCenter {
@ExceptionHandler({Exception.class})
public ResponseResult<String> handleException(Exception e){
ResponseResult<String> result = new ResponseResult<>();
if(e instanceof AuthorizationException){
result.setCode(500);
result.setMessage("你没有权限!");
result.setStatus("NO_AUTHORIZED");
}
return result;
}
}
让TeacherController继承该类
public class TeacherController extends ExceptionCenter{}
四、Session管理
shiro也为用户提供一个session,程序员可以通过该session在任何一层获取到session中的数据
与HttpSession有所不同,具体见下图
4.1 Session存取数据
4.1.1 获取Session
该session可以通过subject对象获取
4.1.2 存储数据
UserController中向Session存储数据
//1.获取到“当前用户”对象
Subject currentUser = SecurityUtils.getSubject();
//获取session(shiro的session)
Session session = currentUser.getSession();
session.setAttribute("name","wangwu");
4.1.3 获取数据
可以在Service实现类中获取到数据
@Override
public User findUserByAccount(String account) {
//从shiro中的session获取数据
System.out.println(SecurityUtils.getSubject().getSession().getAttribute("name"));
return userMapper.findUserByAccount(account);
}
4.2 Session管理器
4.2.1 配置Session管理器
shiro配置类中:
//7. shiro session的管理
//7.1 配置session管理器
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 去掉shiro登录时url里的JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
// 过期时间:单位 毫秒
sessionManager.setGlobalSessionTimeout(30*60*1000);
// 删除已经过期的session
sessionManager.setDeleteInvalidSessions(true);
return sessionManager;
}
4.2.2 向SecurityManager注入Session管理器
shiro配置类中:
//2.安全管理器(核心)
@Bean //参数将相当于 <property ref = "realm">
public SecurityManager securityManager(CustomRealm realm,DefaultWebSessionManager sessionManager){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
//设置realm
manager.setRealm(realm);
//7.2 注入session的管理器
manager.setSessionManager(sessionManager);
//
return manager;
}
五、Shiro标签 - 基于thymeleaf
5.1 pom.xml中导入依赖
<!-- thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro标签 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
5.2 shiro配置类
//8.配置shiro标签的方言
@Bean
public ShiroDialect dialect(){
return new ShiroDialect();
}
5.3 修改main.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/js/jquery.min.js"></script>
<script>
$(function () {
$("#logout").click(function () {
$.ajax({
url:"/user/logout",
success:function (res) {//json
console.log(res);
window.location.href = "/login.html";
}
});
});
})
</script>
</head>
<body>
<p>用户的后台管理页面</p>
<!--
hasRole 具有该角色,才能够看到
hasAnyRole 具有其中任何一个角色都可以看到,就是or,多个角色用逗号隔开
hasAllRole 必须具备所有角色才可以看到,就是and
lacksRole 没有该角色可以看到
hasPermission 具有某个权限才能看到
hasAllPermissions 必须具备所有权限才能看到
lacksPermissions 没有该权限可以看到
authenticated 认证过才可以看到
notAuthenticated 没有认证时能看到
guest 只有游客能看到
principal 获取到当前用户的账号
-->
<a href="/admin.html" shiro:hasRole="superAdmin">教师管理</a><br>
<a href="/teacher.html" shiro:hasAllRoles="admin,teacher">成绩管理</a><br>
<a href="/student.html" shiro:hasAllRoles="admin,teacher,student">分数查询</a><br>
<a href="/adminAndTeacher.html" shiro:hasAllRoles="admin,teacher">密码管理</a><br>
<a href="/adminOrTeacher.html" shiro:lacksRole="admin">信息查询</a><br>
<button id="logout">注销</button>
</body>
</html>
将main.html移动到templates目录下,该目录专门用来存放使用了模板引擎的页面
而且直接访问main.html不会生效,因为采用了模板引擎的页面必须通过后台代码处理
**
5.4 修改JumpController
//去主页面
@RequestMapping("/main")
public String toMain(){
System.out.println("通过后台跳转到首页");
return "/main.html";
}
5.5 修改login.html
success:function (res) {
console.log(res);
//请求后台跳转到首页
if(res=="success"){
//跳转页面
window.location.href="/jump/main";
}
}
六、Remember Me
在shiro有一个过滤器叫做user,用user修饰的url可以在第一次然后没有注销的情况下,下一次可以直接访问
在static下创建rm.html
<html>
<body>
remember me
</body>
</html>
修改login.html,添加记住我复选框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="/js/jquery.min.js"></script>
<script>
$(function () {
$("#login").click(function () {
let account = $("#account").val();
let pwd = $("#pwd").val();
let rm = $("rm").prop("checked");
$.ajax({
url:"user/login",
type:"POST",
data:{
account:account,
pwd:pwd,
rm:rm
},
success:function (res) {
console.log(res);
//请求后台跳转到首页
if(res=="success"){
window.location.href="/jump/main";
}
}
});
});
});
</script>
</head>
<body>
<div>
<input type="text" id="account"><br>
<input type="password" id="pwd"><br>
<input type="checkbox" id="rm">记住我<br>
<button id="login">登录</button>
</div>
</body>
</html>
修改User,添加记住我属性
@Data
@Accessors(chain = true)
public class User {
private int id;
private String account;
private String pwd;
private List<Role> roles;
private boolean rm; //记住我选项
}
修改UserController登录方法,判断用户是否勾选记住我
@RestController //
@RequestMapping("/user")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@RequestMapping("/login")
public String login(User user){
//1.获取到“当前用户”对象
Subject currentUser = SecurityUtils.getSubject();
//获取session
Session session = currentUser.getSession();
session.setAttribute("name","lisi");
//2.判断用户是否认证过
if (!currentUser.isAuthenticated()){
//没有认证过 token 令牌
String pwd = new SimpleHash("MD5",user.getPwd(),user.getAccount(),100).toString();
UsernamePasswordToken token =
new UsernamePasswordToken(user.getAccount(),pwd);
//判断用户是否勾选记住我
if (user.isRm()){
token.setRememberMe(true);//使用记住我功能
}
try {
currentUser.login(token);
//
logger.info("认证成功");
return "success";
}catch(Exception e)
return "密码错误";
}
}
return "success";
}
}
在shiro的过滤器中使用user过滤器配置url
//使用记住我过滤器
map.put("/rm.html","user");
运行项目测试
测试步骤
先正常登陆
关闭浏览器
访问main页面
登陆勾选记住我
访问rm.html
关闭浏览器
访问main.html 不能访问
访问rm.html 可以访问
注意:全程不要注销