springboot 整合shiro实现权限控制-枫桥
shiro
Shiro是Apache旗下的一个开源项目,它是一个非常易用的安全框架,提供了包括认证、授权、加密、会话管理等功能,与Spring Security一样属基于权限的安全框架,但是与Spring Security 相比,Shiro使用了比较简单易懂易于使用的授权方式。Shiro属于轻量级框架,相对于Spring Security简单很多,并没有security那么复杂。
以下是我自己的学习心得
在这里说一下,b站的【狂神说Java】SpringBoot整合Shiro框架讲解的非常好,大家可以去看一下
1、shiro的优点
- 一个功能强大、灵活的、优秀的、开源的安全框架。
- 可以胜任身份验证、授权、企业会话管理和加密等工作。
- 相比 Spring Security,入门门槛低,更易于使用和理解
2、主要的功能有
- Subject:主体,一般指用户。 (全局静态)
- SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。
- Realms:用于进行权限信息的验证,一般需要自己实现。 (自定义权限认证和授权,可以结合redis或者数据库使用)
(更多关于shiro是资料在这里就不详细展示了)
springboot整合shiro
首先先创建springboot项目,此处不过多描述。
1、目录结构
2、依赖管理 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fengqiao</groupId>
<artifactId>fengqiao-login</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<shiro.version>1.5.0</shiro.version>
<java.version>1.8</java.version>
<nekohtml.version>1.9.22</nekohtml.version>
</properties>
<dependencies>
<!-- spring boot start -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency><!--页面模板依赖-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- spring boot end -->
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 非严格模式thymeleaf -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<version>${nekohtml.version}</version>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.fengqiao.myLogin.MyLoginApplication</mainClass>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/java</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
</project>
3、配置文件application.yml
shiro可以不用配置application.yml,后面通过Java文件来配置,这里就配置thymeleaf
spring:
thymeleaf:
cache: false # 开发时关闭缓存,不然没法看到实时页面
mode: HTML
encoding: UTF-8
servlet:
content-type: text/html
prefix: classpath:/templates/
suffix: .html
3、实体类entity
User.java 用户实体类
我这里使用了lombok插件,不想安装的可以手动设置全参数构造方法和Get,Set方法
@Data
@AllArgsConstructor
public class User {
private String id;
private String userName;
private String password;
// 用户对应的角色集合
private Set<Role> roles;
}
Role.java 角色实体类
@Data
@AllArgsConstructor
public class Role {
private String id;
private String roleName;
//角色对应权限集合
private Set<Permissions> permissions;
}
Permissions.java 角色实体类
@Data
@AllArgsConstructor
public class Permissions {
private String id;
private String permissionsName;
}
4、service类
Loginservice.java
public interface LoginService {
User getUsername(String username) ;
}
LoginserviceImpl.java
目前使用map在内存上存储数据,等下次更新的时候,在使用数据库保存读取数据,使用经典的用户-角色-权限关系表
@Service
public class LoginServiceImpl implements LoginService {
@Override
public User getUsername(String username) {
return map.get(username);
}
// 暂时使用静态变量存储数据
private static Map<String ,User> map = new HashMap<>();
static {
//共添加两个用户,
// 第一个用户都是admin角色,拥有所有权限
// 第一个用户都是fengqiao,只拥有add权限无update权限
// 权限
Permissions permissions1 = new Permissions("1","user:update");
Permissions permissions2 = new Permissions("2","user:add");
// 角色权限关系
Set<Permissions> adminSet = new HashSet<>();
adminSet.add(permissions1);
adminSet.add(permissions2);
Set<Permissions> fengqiaoSet = new HashSet<>();
fengqiaoSet.add(permissions2);
// 角色, 目前一个用户对应一个角色
Role adminRole = new Role("1","admin",adminSet);
Role fengqiaoRole = new Role("2","fengqiao",fengqiaoSet);
// 角色用户关系
Set<Role> adminRoleSet = new HashSet<>();
adminRoleSet.add(adminRole);
Set<Role> fengqiaoRoleSet = new HashSet<>();
fengqiaoRoleSet.add(fengqiaoRole);
// 用户
User admin = new User("1","admin","123456",adminRoleSet);
User fengqiao = new User("2","fengqiao","123456",fengqiaoRoleSet);
map.put(admin.getUserName(), admin);
map.put(fengqiao.getUserName(), fengqiao);
}
}
CustomRealm.java
自定义Realm权限管理器
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//根据用户名去数据库查询用户信息
User user = loginService.getUsername(name);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//添加权限
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
}
}
return simpleAuthorizationInfo;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (authenticationToken.getPrincipal() == null) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
User user = loginService.getUsername(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
}
ShiroConfig.java
shiro配置文件,将配置上面自定义的CustomRealm
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//将自己的验证方式加入容器
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 使用map存储权限规则
Map<String, String> map = new HashMap<>();
map.put("/user/add","perms[user:add]");
map.put("/user/update","perms[user:update]");
//首页
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//错误页面跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
MyExceptionHandler.java
注解配置类,用于处理异常
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
System.out.println("没有通过权限验证!"+e);
return "没有通过权限验证!";
}
}
5、Controller层
LoginShiroController
登录页面controller层
@Controller
public class LoginShiroController {
@RequestMapping({"/" , "/index"})
public String toIndex(Model model) {
model.addAttribute("msg","hello, shiro ");
return "index";
}
@RequestMapping("/user/add")
public String useradd(Model model) { return "user/add"; }
@RequestMapping("/user/update")
public String userupdate(Model model) { return "user/update"; }
@RequestMapping("/toLogin")
public String toLogin(Model model) { return "user/login"; }
@RequestMapping("/auth")
public String auth(String username, String password ,Model model) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
System.out.println(username+password);
try{
subject.login(token);
}catch (UnknownAccountException e){ // 用户名异常
model.addAttribute("msg","用户名不存在");
return "user/login";
}catch (IncorrectCredentialsException e){ // 密码错误
model.addAttribute("msg","密码错误");
return "user/login";
}
return "index";
}
@RequestMapping("/noAuth")
public String noAuth(Model model) {
model.addAttribute("msg","未经授权无法访问");
return "noAuth";
}
//注解验角色和权限
@RequiresRoles("fengqiao")
@RequiresPermissions("user:add")
@RequestMapping("/restindex")
public String index() {
return "index";
}
}
6、thymeleaf 页面
本系统是一个简单的springboot整合shiro的项目,页面写的简单了点,但麻雀虽小,还得五脏俱全
add.html 添加
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>user add</h1>
</body>
</html>
update.html 更新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>user update</h1>
</body>
</html>
login.html 登录页
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
<form method="post" action="/auth">
<table>
<tr><td>用户名</td><td><input type="text" name="username" /></td></tr>
<tr><td>密码</td><td><input type="password" name="password" /></td></tr>
<tr><td><input type="submit" /></td></tr>
</table>
</form>
</body>
</html>
index.html 首页
<!DOCTYPE>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello shrio</title>
</head>
<body>
<div>
<span>首页</span>
<h1 th:text="${msg}"></h1>
<a href="/user/add">useradd</a>
<a href="/user/update">userupdate</a>
</div>
</body>
</html>
noAuth.html 未认证页面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
</body>
</html>
测试
这里使用fengqiao账号为大家演示下
http://localhost:8080/auth
用户名:fengqiao
密码: 123456
http://localhost:8080/index
在这页面有两个超链接,useradd有权限,userupdate没有权限,返回未认证