Spring Boot学习篇(十三)
shiro安全框架使用篇(五)
1 准备工作
1.1 在SysUserMapper.xml中书写自定义标签
<select id="findRoles" resultType="string">
select name from sys_role where id = (select roleid from sys_user_role where userid = (SELECT id FROM `sys_user` where username = #{username}))
</select>
<select id="findPerms" resultType="string">
select name from sys_permission where id in
(select perid from sys_role_permission where roleid in
(select roleid from sys_user_role where userid in
(SELECT id FROM `sys_user` where username =#{username})))
</select>
1.2 SysUserMapper接口书写自定义标签所对应的方法
//根据用户名查询角色
List<String> findRoles(String username);
//根据用户名查询权限
List<String> findPerms(String username);
1.3 login.html界面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2 th:if="${msg!=null}" th:text="${msg}"></h2>
<form action="/users/login" method="post">
<!--需要与控制器保持一致-->
用户名:<input type="text" name="yhm">
密码:<input type="password" name="mm">
记住我: <input type="checkbox" name="jzw">
<button>登录</button>
</form>
</body>
</html>
1.4 index.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>
</head>
<body>
主页
<a href="/zhuxiao">注销</a>
<a href="/songs/find">查询商品</a>
<a href="/songs/update">更改商品</a>
<a href="/songs/insert">添加商品</a>
<a href="/songs/delete">删除商品</a>
</body>
</html>
1.5 qx.html界面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>你没有该访问权限</h1>
<a href="/index.html">点击进入主页</a>
</body>
</html>
1.6 SongsController类
package com.zlz.controller;
import com.zlz.entity.Songs;
import com.zlz.service.ISongsService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/**
* <p>
* 前端控制器
* </p>
*
* @author zlz
* @since 2023-02-04
*/
//跨域的问题 1.协议不同 2.地址不同 3.端口不同 三者满足其一,就是跨域
//(origins = "*") origins仅允许指定域名访问 (origins = "http://127.0.0.1:8848")
@CrossOrigin
@Controller
@RequestMapping("/songs")
public class SongsController {
@Autowired
ISongsService iSongsService;
@RequestMapping("select")
@ResponseBody
public List<Songs> s(){
return iSongsService.list();
}
@RequestMapping("find")
public String a(){
System.out.println("查询数据");
return "index";
}
@RequestMapping("delete")
//
//角色,需要自定义异常
@RequiresRoles(value = {"经理","组长"},logical = Logical.OR)
//判断是否具有权限的
// @RequiresPermissions()
public String b(){
System.out.println("删除数据");
return "index";
}
@RequiresUser
@RequestMapping("update")
public String c(){
System.out.println("修改数据");
return "index";
}
@RequestMapping("insert")
public String d(){
System.out.println("添加数据");
return "index";
}
}
2.操作授权(全满足)
2.1 在MySqlRealm类里面(继承了AuthorizingRealm抽象类)书写授权方法的内容
@Resource
SysUserMapper sysUserMapper;
//授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection p) {
//注意看方法的返回值,一般是返回方法返回值类型所对应的实现类(Simple开头+方法返回值类型)
//①得到当前登录的用户名
String yhm =(String)p.getPrimaryPrincipal();
//②查询到该用户的角色和权限
List<String> roles = sysUserMapper.findRoles(yhm);
List<String> perms = sysUserMapper.findPerms(yhm);
//③ 控制台打印 方便查看效果
System.out.println(yhm+"具有权限"+roles);
System.out.println(yhm+"具有角色"+perms);
//④授权并返回
SimpleAuthorizationInfo sm=new SimpleAuthorizationInfo();
sm.addRoles(roles);
sm.addStringPermissions(perms);
return sm;
}
2.2 在ShiroConfig类的factoryBean方法中配置控制器过滤
//默认是roles[角色名1,角色名2...角色名n],因为底层用的是hasAllRoles方法,因此是必须含有全部权限
//下面代码表示经理和组长的权限都要满足才可以进行删除
map.put("/songs/delete","user,roles[经理,组长]");
2.3 在ShiroConfig类中的factoryBean方法配置没有权限时的跳转地址
//检测到没有权限时的地址
sb.setUnauthorizedUrl("/qx.html");
2.4 设置没有权限时跳转的页面(templates目录下)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>你没有该访问权限</h1>
<a href="/index.html">点击进入主页</a>
</body>
</html>
2.5 测试
2.5.1 使用admin用户(具有经理权限)登录后并点击删除商品进入如下界面
2.5.2 使用aaa用户(具有组长权限)登录后并点击删除商品进入如下界面
3.操作授权(满足其中之一)
3.1 自定义角色过滤器类MyRolesFilter(继承RolesAuthorizationFilter类)
package com.zlz.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* 自定义角色过滤器
*/
public class MyRolesFilter extends RolesAuthorizationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = this.getSubject(request, response);
String[] rolesArray = (String[])mappedValue;
if (rolesArray != null && rolesArray.length != 0) {
Set<String> roles = CollectionUtils.asSet(rolesArray);
boolean b=false;
for (String role : roles) {
//满足其中之一就返回,hasRole满足条件就为true,
if(subject.hasRole(role)){
b=true;
break;
}
}
return b;
} else {
return true;
}
}
}
3.2 核心代码(在ShiroConfig类中factoryBean方法中)
/**
* 自定义过滤器 换个名字好点 sb是ShiroFilterFactoryBean类型的对象
*/
Map<String, Filter> myFilter=new HashMap<>();
myFilter.put("myroles",new MyRolesFilter());
sb.setFilters(myFilter);
//设置删除商品需要经理或者组长其一的权限
map.put("/songs/delete","user,myroles[经理,组长]");
3.3 完整的ShiroConfig类代码
package com.zlz.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.zlz.filter.MyRolesFilter;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//shiro标签库
@Bean
public ShiroDialect sd(){
return new ShiroDialect();
}
//配置安全管理器 shiro核心配置类
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager dms=new DefaultWebSecurityManager();
//设置域(查询数据库dao类) 查哪里的数据库设置
dms.setRealm(realm());
//设置会话管理器
dms.setSessionManager(new DefaultWebSessionManager());
//设置记住我管理器
dms.setRememberMeManager(ck());
return dms;
}
@Bean
public Realm realm(){
MysqlRealm r=new MysqlRealm();
//设置密码加密 登录是加密比对
HashedCredentialsMatcher hsm = new HashedCredentialsMatcher();
hsm.setHashAlgorithmName("sha-256");
hsm.setHashIterations(100);
r.setCredentialsMatcher(hsm);
return r;
}
//shiro过滤器(核心)
@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean factoryBean(){
ShiroFilterFactoryBean sb=new ShiroFilterFactoryBean();
//设置安全管理器
sb.setSecurityManager(securityManager());
/*
基本设置
*/
//检测到没有登录的地址
sb.setLoginUrl("/login.html");
//检测到没有权限时的地址(这里是一种设置没有权限时候跳转的地址)
sb.setUnauthorizedUrl("/qx.html");
/**
* 自定义过滤器 换个名字好点 sb是ShiroFilterFactoryBean类型的对象
是通过map集合设置进去的
*/
Map<String, Filter> myFilter=new HashMap<>();
myFilter.put("myroles",new MyRolesFilter());
sb.setFilters(myFilter);
//控制器过滤设置 拦截有顺序//
/**
* anon 允许匿名访问 无需登录
* user 需要登录后才能访问
* roles 需要具有所有的角色
* perms 需要具有所有的权限
* logout 注销过滤器
* authc 不包含记住我 需要登录
*/
Map<String,String> map=new LinkedHashMap<>();
map.put("/*.html", "anon");
//注销流程 自己清空session
map.put("/zhuxiao", "logout");
map.put("/songs/find","anon");
//myroles就是和之前定义的key值保持一致
map.put("/songs/delete","user,myroles[经理,组长]");
//放行写上面 拦截写下面
sb.setFilterChainDefinitionMap (map);
return sb;
}
@Bean
public CookieRememberMeManager ck(){
CookieRememberMeManager c=new CookieRememberMeManager();
SimpleCookie sc=new SimpleCookie();
sc.setHttpOnly(true);//防止js读取cookie
sc.setMaxAge(60);//设置cookie时长
sc.setName("zlz");
c.setCookie(sc);
return c;
}
}
3.3 测试
3.3.1 组长权限(用户名为aaa)
a 点击删除按钮后跳转的界面
b 点击删除按钮后的idea控制台界面
3.3.2 管理员权限(用户名为admin)
a 点击删除按钮后跳转的界面
b 点击删除按钮后的idea控制台界面
4 设置注销后跳转的地址
4.1 核心代码
//Map<String, Filter> myFilter=new HashMap<>();
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setRedirectUrl("/index.html");//修改注销后的跳转地址,如果没有设置默认跳转的是login.html界面
myFilter.put("logout",logoutFilter);