SpringBoot 整合 Shiro 实现权限管理,rabbitmq 原理图
====================================================================
用户密码一般不会以明文方式保存,这样无法保证安全性,所以一般都需要加密。
SimpleHash 类可以实现基本的加密,几种创建方式:
new SimpleHash("加密算法","原始密码")
new SimpleHash("加密算法","原始密码",盐)
new SimpleHash("加密算法","原始密码",盐,迭代次数)
参数说明:
加密算法一般使用常用的 md5 算法
盐的作用是提高密码安全性,如两个用户的原始密码都是 123,则加密后的密文都是相同的,如果破解了一个用户的密码,另一个用户的密码也一同破解了,如果给密码加盐,每个用户的盐不同,加密后密码就都会不同,增加了破解难度。
迭代次数是加密一次后,再对密文再次加密,也能提高安全性。
下面我们以 md5 算法对“123456”加密,盐是“007”,迭代次数为 10。
SimpleHash md5 = new SimpleHash("md5", "123456",ByteSource.Util.bytes("007"), 10);
System.out.println(md5);
输出:44202d045439dc33a2e43d2828d08e19
修改 MyRealm 的 doGetAuthenticationInfo 方法,这里将密文和盐直接写在代码中,实际应用时密文和盐是通过用户名从数据库中查询出来的。
//返回验证信息,参数:1、用户名 2、正确密码 3、盐 4、realm 名称
return new SimpleAuthenticationInfo(username,"44202d045439dc33a2e43d2828d08e19", ByteSource.Util.bytes("007"),getName());
需要给自定义 Realm 添加密码匹配器
//创建默认安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//安全管理器配置自定义 Realm
MyRealm realm = new MyRealm();
//创建密码匹配器
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
//设置迭代次数
md5.setHashIterations(10);
//配置匹配器
realm.setCredentialsMatcher(md5);
securityManager.setRealm(realm);
//SecurityUtils 配置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//获得 Subject 对象
Subject subject = SecurityUtils.getSubject();
//创建账号密码 token
UsernamePasswordToken user = new UsernamePasswordToken("zhang", "123456");
//登录验证
subject.login(user);
//权限判断
System.out.println("是否登录成功:" + subject.isAuthenticated());
System.out.println("是否拥有 role1 角色:" + subject.hasRole("role1"));
System.out.println("是否拥有 delete 权限:" + subject.isPermitted("user:delete"));
========================================================================================
1、表设计
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XS3cRTkB-1608710040674)(shiro.assets/1608618965879.png)]](https://img-blog.csdnimg.cn/20201223155947307.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMzNDMxMTQ=,size_16,color_FFFFFF,t_
70#pic_center)
s_user 用户表
s_role 角色表
s_menu 菜单表(权限表)
s_user_menu 用户角色中间表
s_role_menu 角色菜单中间表
2、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2、SpringBoot 配置
jdbc 配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/erp_db?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
mybatis 配置
mybatis-plus.type-aliases-package=com.blb.blb_erp.entity
mybatis-plus.mapper-locations=classpath:mapper/*.xml
shiro 配置
登录页面
shiro.loginUrl=/pages/login.html
登录失败跳转页面
shiro.unauthorizedUrl=/pages/failed.html
登录成功跳转页面
shiro.successUrl=/pages/index.html
3、编写 Mapper 接口
需要三个方法:
按用户名查找用户
按用户 id 查询所有菜单
按用户 id 查询所有角色
/**
用户接口
*/
public interface SUserMapper extends BaseMapper<SMenu>{
/**
通过用户名查询用户
@param username
@return
*/
SUser selectUserByUsername(String username);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blb.blb_erp.mapper.SUserMapper">
<select id="selectUserByUsername" resultType="SUser">
select * from s_user where user_name = #{username}
</select>
</mapper>
/**
菜单接口
*/
public interface SMenuMapper extends BaseMapper<SMenu>{
/**
根据 userId 查询所有权限
@param userId
@return
*/
List<SMenu> selectMenusByUserId(String userId);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blb.blb_erp.mapper.SMenuMapper">
<select id="selectMenusByUserId" resultType="SMenu">
select m.* from s_user u,s_role r,s_user_role ur,s_menu m,s_role_menu rm
where ur.role_id = r.id and ur.user_id = u.id and rm.role_id = r.id and rm.menu_id = m.id
and u.id = #{userId}
</select>
</mapper>
/**
角色接口
*/
public interface SRoleMapper extends BaseMapper<SMenu>{
/**
根据用户 id 查询所有角色
@param userId
@return
*/
List<SRole> selectRolesByUserId(String userId);
}
映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.blb.blb_erp.mapper.SRoleMapper">
<select id="selectRolesByUserId" resultType="SRole">
select r.* from s_user_role ur
join s_user u on ur.user_id = u.id
join s_role r on ur.role_id = r.id
where ur.user_id = #{userId}
</select>
</mapper>
4、自定义 Realm
/**
用户 Realm
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private SUserMapper userMapper;
@Autowired
private SRoleMapper roleMapper;
@Autowired
private SMenuMapper menuMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获得用户对象
SUser user = (SUser) principalCollection.getPrimaryPrincipal();
//查询权限和角色
List<SMenu> menus = menuMapper.selectMenusByUserId(user.getId());
List<SRole> roles = roleMapper.selectRolesByUserId(user.getId());
//保存权限和角色名称的集合
List<String> strRoles = new ArrayList<>();
roles.forEach(r -> strRoles.add(r.getRoleName()));
List<String> strMenus = new ArrayList<>();
menus.forEach(m -> strMenus.add(m.getMenuName()));
//返回带有角色和权限名称的授权信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(strRoles);
info.addStringPermissions(strMenus);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获得账号
String username = authenticationToken.getPrincipal().toString();
//通过账号查询用户
SUser user = userMapper.selectUserByUsername(username);
if(user == null){
throw new UnknownAccountException("此用户不存在");
}
//返回验证信息 参数:1、用户对象 2、正确密码 3、盐 4、realm 名称
return new SimpleAuthenticationInfo(user,user.getPassWord(), ByteSource.Util.bytes(user.getSalt()),getName());
}
}
5、Shiro 配置类
/**
Shiro 配置
*/
@Configuration
public class ShiroConfig {
//返回 Realm
@Bean
public UserRealm myRealm(){
UserRealm myRealm = new UserRealm();
//设置密码匹配器
HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");
md5.setHashIterations(10);
myRealm.setCredentialsMatcher(md5);
//关闭缓存
myRealm.setCachingEnabled(false);
return myRealm;
}
//返回面向 Web 开发的安全管理器
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
//设置自定义 Realm
sm.setRealm(myRealm());
return sm;
}
//返回 Shiro 过滤器链定义
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
//定义过滤器链 key 为 url,value 为 anon 不验证,authc 验证
//anon 在前,authc 在后,需要使用 LinkedHashMap 保留顺序
LinkedHashMap<String, String> map = new LinkedHashMap<>();
map.put("/pages/login.html","anon");
map.put("/user/login","anon");
map.put("/**","authc");
chainDefinition.addPathDefinitions(map);
return chainDefinition;
}
//启动 thymeleaf 的 shiro 标签
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
6、启动类
@MapperScan("com.blb.blb_erp.mapper")
@SpringBootApplication
public class BlbErpApplication {
public static void main(String[] args) {
SpringApplication.run(BlbErpApplication.class, args);
}
}
7、控制器
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JsonResult {
private int code;
private Object data;
}
@RestController
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public JsonResult login(String username,String password){
//创建 Token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//获得 subject
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return new JsonResult(1,"登录成功");
}catch (AuthenticationException ex){
ex.printStackTrace();
}
return new JsonResult(0,"账号或密码错误");
}
@RequiresRoles("管理员")
@GetMapping("/role-admin")
public String testRole(){
return "有管理员角色";
}
@RequiresPermissions("部门管理")
@GetMapping("/menu-dept")
public String testMenu(){
return "有部门管理权限";
}
}
@RequiresRoles、@RequiresPermissions 写在控制器的方法上,登录用户有对应的角色和权限才能访问。
========================================================================
可以在登录页面上添加记住我功能,勾选后下次不用登录直接进去系统了
1、页面上添加 RememberMe 复选框
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" href="/elementui/index.css">
<style>
.box-card{
margin:200px auto;
width: 480px;
}
.clearfix{
text-align: center;
color:#303133;
font-size: 18px;
}
.login-form{
width: 400px;
}
</style>
</head>
<body>
<div id="app">
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>系统登录</span>
</div>
<el-form class="login-form" ref="form" :model="form" label-width="80px">
<el-form-item label="账号">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="form.password"></el-input>
</el-form-item>
<el-form-item >
<el-checkbox v-model="form.rememberMe">记住我</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login">登 录</el-button>
<el-button>取 消</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
<script src="/vue/vue.js"></script>
<script src="/elementui/index.js"></script>
<script src="/axios/axios.min.js"></script>
<script src="/qs/qs.min.js"></script>
<script>
new Vue({
el:"#app",
data:{
form:{username:"",password:"",rememberMe:false}
},
methods:{
login:function () {
//Qs.stringify(this.form) 将 form 由{xx:值} 转为 xx=值 &xx=值
axios.post("/user/login",Qs.stringify(this.form))
.then(res=>{
if(res.data.code == 1){
location.href = "/pages/index.html";
}
});
}
}
});
</script>
</body>
</html>
2、修改登录方法
@PostMapping("/login")
public JsonResult login(String username,String password,Boolean rememberMe){
//创建 Token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//设置记住我
token.setRememberMe(rememberMe);
//获得 subject
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return new JsonResult(1,"登录成功");
}catch (AuthenticationException ex){
ex.printStackTrace();
}
return new JsonResult(0,"账号或密码错误");
}
3、修改 Shiro 配置类
//创建 RememberMe 管理器
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
SimpleCookie rememberMe = new SimpleCookie("rememberMe");
//单位是秒 过期时间
rememberMe.setMaxAge(60 * 10);
rememberMeManager.setCookie(rememberMe);
return rememberMeManager;
}
//返回面向 Web 开发的安全管理器
@Bean
评论