Shiro
Shiro
- Shiro官网:http://shiro.apache.org/
- SpringBoot版本:2.2.5.RELEASE
QuickStart
- 主要方法(Spring Security都有)
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
currentUser.isAuthenticated()
currentUser.getPrincipal()
currentUser.hasRole("schwartz")
currentUser.isPermitted("lightsaber:wield")
currentUser.logout();
SpringBoot-Shiro
环境搭建
添加依赖
<!--
Subject: 用户
SecurityManager: 管理所有用户
Realm: 连接数据库
-->
<!-- SpringBoot-Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
编写页面跳转路由Controller
-
涉及的页面就简单写一下就行
-
templates/index.html
-
templates/user/add.html
-
templates/user/update.html
-
@Controller
public class MyController {
@RequestMapping({"/", "/index"})
public String toIndex(Model model) {
model.addAttribute("msg", "helloShiro");
return "index";
}
@RequestMapping("/add")
public String toAdd() {
return "user/add";
}
@RequestMapping("/update")
public String toUpdate() {
return "user/update";
}
}
编写自定义的Realm
// 自定义 realm
public class UserRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
return null;
}
}
在SpringBoot中配置Shiro
@Configuration
public class ShiroConfig {
// Step 3: ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(manager);
return bean;
}
// Step 2: DefaultWebSecurityManager
// @Qualifier("userRealm") 中间参数为变量名, 可以锁定一个参数
@Bean(name = "defaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 关联 userRealm
manager.setRealm(userRealm);
return manager;
}
// Step 1: 创建 realm 对象, 需要自定义
@Bean(name = "userRealm")
public UserRealm userRealm() {
return new UserRealm();
}
}
登录拦截
在ShiroFilterFactoryBean中添加过滤器
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(manager);
// 添加Shiro的内置过滤器
/*
anon: 无需认证即可访问
authc: 必须认证才能访问
user: 必须拥有 记住我 功能才能用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
// add 不用登录
filterMap.put("/user/add", "anon"); // anon 别写错了
// update 需要登录
filterMap.put("/user/update", "authc");
// filterMap.put("/user/*", "authc"); 通配符也支持
bean.setFilterChainDefinitionMap(filterMap);
// 未登录情况下, 需要登录跳转的页面链接
bean.setLoginUrl("/toLogin");
return bean;
}
用户认证
Controller添加login方法
- username与password由前端传递
@RequestMapping("/login")
public String login(String username, String password, Model model) {
// 获取当前用户
Subject subject = SecurityUtils.getSubject();
// 封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); // 执行登录方法, 如果没异常就OK了
} catch (UnknownAccountException e) { // 用户名不存在
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e) { // 密码错误
model.addAttribute("msg", "密码错误");
}
return "index";
}
在UserRealm中编写认证方法
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
// 用户名, 密码
String name = "d";
String pwd = "1";
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
if (!token.getUsername().equals(name)) {
return null; // 抛出异常, UnknownAccountException
}
// 密码认证, Shiro完成
return new SimpleAuthenticationInfo("", pwd, "");
}
整合Mybatis
添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
在application.yaml中配置JDBC、Mybatis和Druid
spring:
datasource:
username: root
password: lu123
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
type-aliases-package: com.lu.pojo
mapper-locations: classpath:mapper/*.xml
编写用户类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
在认证过程中使用数据库查找到的数据
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 连接真实数据库
User user = service.findByName(token.getUsername());
if (user == null) {
return null; // 抛出异常, UnknownAccountException
}
// 密码认证, Shiro完成
return new SimpleAuthenticationInfo("", user.getPwd(), "");
}
用户授权
更改User实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
private String perms; // 权限
}
在ShiroFilterFactoryBean方法中添加请求的访问权限
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager manager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置安全管理器
bean.setSecurityManager(manager);
// 添加Shiro的内置过滤器
/*
anon: 无需认证即可访问
authc: 必须认证才能访问
user: 必须拥有 记住我 功能才能用
perms: 拥有对某个资源的权限才能访问
role: 拥有某个角色权限才能访问
*/
// 过滤器放进去的键值对有顺序, 先处理子请求再处理父请求
Map<String, String> filterMap = new LinkedHashMap<>();
// 权限, 正常情况下未授权会转发到未授权页面
filterMap.put("/user/add", "perms[user:add]");
filterMap.put("/user/update", "perms[user:update]");
// 拦截
filterMap.put("/user/*", "authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/toLogin");
// 未授权页面
bean.setUnauthorizedUrl("/unauth");
return bean;
}
编写未授权的跳转页面
@RequestMapping("/unauth")
@ResponseBody
public String unauth() {
return "Unauthorized User!!!!";
}
增添授权操作,通过数据库获取权限
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
// SimpleAuthorizationInfo
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 拿到当前登录的对象
Subject subject = SecurityUtils.getSubject();
User cur_user = (User) subject.getPrincipal();
// 设置当前用户权限
info.addStringPermission(cur_user.getPerms());
return info;
}
整合Thymeleaf
添加依赖
<!--Shiro-Thymeleaf整合-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
添加命名空间头文件
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
在类ShiroConfig中注册Bean
// 整合ShiroDialect: 用于整合Shiro和Thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
修改index.html页面逻辑
- 有什么权限显示什么链接
- 登录后不显示登录页面
<body>
<h1>首页</h1>
<div th:text="${msg}" style="color: red"></div>
<br>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">Add</a>
</div>
<br>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">Update</a>
</div>
<br>
<div shiro:notAuthenticated>
<a th:href="@{/toLogin}">登录</a>
</div>
<!-- <a th:href="@{/toLogin}">Login</a>-->
</body>
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决