建个项目或者模块,目录结构如下
在 pom.xml 中加入 shiro 依赖,其他依赖自行添加 (lombok,jpa,mybatis,web,thymeleaf 等)
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
application.properties 中的配置
## 端口号
server.port=8888
## 数据库配置
## 数据库地址
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false\
&serverTimezone=GMT%2B8
## 数据库用户名
spring.datasource.username=root
## 数据库密码
spring.datasource.password=Panbing936@
## 数据库驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
##validate 加载 hibernate 时,验证创建数据库表结构
##create 每次加载 hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。##create-drop 加载 hibernate 时创建,退出是删除表结构
##update 加载 hibernate 自动更新数据库结构
##validate 启动时验证表的结构,不会创建表
##none 启动时不做任何操作
spring.jpa.hibernate.ddl-auto=update
## 控制台打印 sql
spring.jpa.show-sql=true
# 建议在开发时关闭缓存, 不然没法看到实时页面
spring.thymeleaf.cache=false
## 去除 thymeleaf 的 html 严格校验
spring.thymeleaf.mode=LEGACYHTML5
#没下面这行配置就会报这个错误
#Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
实体类 SysMenu.java 中的代码
@Entity
@Data
public class SysMenu implements Serializable {
@Id
@GeneratedValue
private Integer menuId;
private String menuName;
@ManyToMany
@JoinTable(name = "SysRoleMenu", joinColumns = {@JoinColumn(name = "menuId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
private List<SysRole> roleList;
}
实体类 SysRole.java 中的代码
@Entity
@Data
public class SysRole implements Serializable {
@Id
@GeneratedValue
private Integer roleId;
private String roleName;
// 多对多关系
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "SysRoleMenu", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "menuId")})
private List<SysMenu> menuList;
// 多对多关系
@ManyToMany
@JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "userId")})
private List<SysUser> userList;// 一个角色对应多个用户
}
实体类 SysUser.java 中的代码
@Entity
@Data
public class SysUser implements Serializable {
@Id
@GeneratedValue
private Integer userId;
@NotEmpty
private String userName;
@NotEmpty
private String passWord;
// 多对多关系
@ManyToMany(fetch = FetchType.EAGER)
// 急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载
//FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载
@JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "userId")},
inverseJoinColumns = {@JoinColumn(name = "roleId")})
private List<SysRole> roleList;// 一个用户具有多个角色
}
接口 UserRepository.java 中的代码
public interface UserRepository extends CrudRepository<SysUser,Long> {SysUser findByUserName(String username);
}
下面的代码才是 shiro 相关的
MyshiroRealm.java
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserRepository userRepository;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUser userInfo = (SysUser)principals.getPrimaryPrincipal();
for(SysRole role:userInfo.getRoleList()){authorizationInfo.addRole(role.getRoleName());
for(SysMenu menu:role.getMenuList()){authorizationInfo.addStringPermission(menu.getMenuName());
}
}
return authorizationInfo;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获得当前用户的用户名
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
// 根据用户名找到对象
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro 自己也是有时间间隔机制,2 分钟内不会重复执行该方法
SysUser userInfo = userRepository.findByUserName(username);
if(userInfo == null){return null;}
// 这里会去校验密码是否正确
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, // 用户名
userInfo.getPassWord(),// 密码
getName());
return authenticationInfo;
}
}
ShiroConfig.java
@Configuration
public class ShiroConfig {private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {logger.info("启动 shiroFilter-- 时间是:" + new Date());
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//shiro 拦截器
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//<!-- authc: 所有 url 都必须认证通过才可以访问; anon: 所有 url 都都可以匿名访问 -->
//<!-- 过滤链定义,从上向下顺序执行,一般将 /** 放在最为下边 -->
// 配置不被拦截的资源及链接
filterChainDefinitionMap.put("/static/**", "anon");
// 退出过滤器
filterChainDefinitionMap.put("/logout", "logout");
// 配置需要认证权限的
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找 Web 工程根目录下的 "/login" 页面,即本文使用的 login.html
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
// 自定义身份认证 Realm(包含用户名密码校验,权限校验等)@Bean
public MyShiroRealm myShiroRealm(){MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
@Bean
public SecurityManager securityManager(){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
// 开启 shiro aop 注解支持,不开启的话权限验证就会失效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
// 配置异常处理,不配置的话没有权限后台报错,前台不会跳转到 403 页面
@Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");// 数据库异常处理
mappings.setProperty("UnauthorizedException","403");
simpleMappingExceptionResolver.setExceptionMappings(mappings); // None by default
simpleMappingExceptionResolver.setDefaultErrorView("error"); // No default
simpleMappingExceptionResolver.setExceptionAttribute("ex"); // Default is "exception"
return simpleMappingExceptionResolver;
}
}
thymeleaf 的页面代码
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
index
<br/>
<form th:action="@{/logout}" method="post">
<p><input type="submit" value="重新登录"/></p>
</form>
<form th:action="@{/select}" method="get">
<p><input type="submit" value="查看"/></p>
</form>
<form th:action="@{/delete}" method="get">
<p><input type="submit" value="删除"/></p>
</form>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
错误信息:<h4 th:text="${msg}"></h4>
<form action=""method="post">
<p> 账号:<input type="text" name="username" value="dalaoyang"/></p>
<p> 密码:<input type="text" name="password" value="123"/></p>
<p><input type="submit" value="登录"/></p>
</form>
</body>
</html>
另外三个跳转页面就不贴出来了,panpan 账号登录可以查看和删除,用 xiaoli 账号登录则只有查看而没有删除的权限,代码见下面,sql 文件在 resources 包下
github 代码
个人网站