# shiro-spring-boot-demo
**Repository Path**: whole-stack-of-white/shiro-spring-boot-demo
## Basic Information
- **Project Name**: shiro-spring-boot-demo
- **Description**: SpringBoot整合Shiro前后端不分离源码
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-12-12
- **Last Updated**: 2024-05-16
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# SpringBoot整合Shiro前后端不分离
## 一、组件说明

来源于百度图片,三个部分,主题Subject、安全管理器SecurityManager、Realm
### 1.2 Shiro配置的几个过滤器
```java
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
authcBearer(BearerHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class),
invalidRequest(InvalidRequestFilter.class);
```
## 二、整合步骤
### 2.1 导入依赖
~~~xml
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-thymeleaf
org.projectlombok
lombok
mysql
mysql-connector-java
5.1.47
com.baomidou
mybatis-plus-boot-starter
3.5.0
org.apache.shiro
shiro-spring-boot-starter
1.8.0
~~~
### 2.2 配置文件
~~~yaml
server:
port: 2022
spring:
# thymeleaf配置
thymeleaf:
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
cache: false
# 数据源配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///shiro-spring-boot-demo?characterEncoding=utf-8&useSSL=false
username: root
password: root
# mp的配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
type-aliases-package: com.cxs.model
mapper-locations: classpath:mapper/*.xml
global-config:
banner: false
~~~
### 2.3 编写认证Realm
AuthorizingRealm有两个方法需要我们重写
- doGetAuthenticationInfo:自定义认证
根据用户输入的用户名查数据库,存在将其封装为一个AuthenticationInfo对象返回,不存在自己处理,这里抛出UnknownAccountException异常,这个异常会在异常处理器处理(后面提)
AuthenticationInfo对象中会有一个载荷,实现类SimpleAuthenticationInfo构造方法中的第一个参数,这个载荷用于认证成功后Shiro存储的实体,可以自定义,我这直接将用户信息存里面了,但是存什么,在 subject.getPrincipal()中取出来的就是什么,建议将用户id和用户名存一下
- doGetAuthorizationInfo:自定义授权
根据认证成功后Shiro存的载荷查询用户应有的角色权限,封装到一个AuthorizationInfo对象中,由Shiro的authc过滤器去判断是否有权限,看下其继承结构

~~~java
/*
* @Project:spring-boot-shiro-demo
* @Author:cxs
* @Motto:放下杂念,只为迎接明天更好的自己
* */
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
public AuthRealm(){
// 注入密码加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashIterations(1024);
matcher.setHashAlgorithmName("MD5");
this.setCredentialsMatcher(matcher);
}
public static void main(String[] args) {
Md5Hash md5Hash = new Md5Hash("user","user",1024);
System.out.println(md5Hash.toString());
}
/**
* 负责认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if (token == null || token.getPrincipal() == null) {
throw new UnknownAccountException("用户不存在!");
}
// 从 AuthenticationToken 中获取当前用户
String username = (String) token.getPrincipal();
// 查询数据库获取用户信息,此处使用 Map 来模拟数据库
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUserName,username);
User user = userService.getOne(wrapper);
// 用户不存在
if (user == null) {
throw new UnknownAccountException("用户不存在!");
}
/**
* 将获取到的用户数据封装成 AuthenticationInfo 对象返回,此处封装为 SimpleAuthenticationInfo 对象。
* 参数1. 认证的实体信息,可以是从数据库中获取到的用户实体类对象或者用户名
* 参数2. 查询获取到的登录密码
* 参数3. 盐值
* 参数4. 当前 Realm 对象的名称,直接调用父类的 getName() 方法即可
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
// 设置盐
info.setCredentialsSalt(ByteSource.Util.bytes(user.getUserName()));
return info;
}
/**
* 负责权限
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Object principal = principals.getPrimaryPrincipal();
User token = (User) principal;
// 这个步骤在认证之后,principal必定不为空
// 根据用户获取对应权限,这里就不获取了,因为权限和用户在一张表里
Set roles = new HashSet<>();
roles.add(token.getRole());
AuthorizationInfo info = new SimpleAuthorizationInfo(roles);
return info;
}
}
~~~
### 2.4 编写Shiro核心配置类
以下两步配置后不会发生无限重定向,去他的不说了,注释都写了
- shiroFilterFactoryBean.setLoginUrl("/login"); 配置登录页面的地址,
- 放行/login路径
~~~java
/*
* @Project:spring-boot-shiro-demo
* @Author:cxs
* @Motto:放下杂念,只为迎接明天更好的自己
* */
@Configuration
public class ShiroConfig {
/**
* 注入realm
* @return
*/
@Bean
public AuthRealm authRealm(){
return new AuthRealm();
}
/**
* 配置 SecurityManager
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置Realm
securityManager.setRealm(authRealm());
return securityManager;
}
/**
* 配置访问资源需要的权限
*/
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 指定登录的地址,请勿指定xxx.html,说过了templates下的文件不能通过浏览器直接访问
shiroFilterFactoryBean.setLoginUrl("/login");
// 自定义过滤器
LinkedHashMap filterChainDefinitionMap = new LinkedHashMap();
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/auth/login", "anon"); // anno可匿名访问
filterChainDefinitionMap.put("/**", "authc"); // 其他所有资源均需登录才能访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//开启对shior注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
}
~~~
### 2.5 编写接口
登陆接口,
User user = (User) subject.getPrincipal();
这里取出来的值是realm中决定的,不一致或发生类型转换异常(ClassCastException)
~~~java
/**
* 用户认证接口
* @param request
* @param model
* @param username
* @param password
* @return
*/
@PostMapping("/auth/login")
public String authLogin(HttpServletRequest request, Model model, String username, String password){
if (!StringUtils.hasLength(username)) {
model.addAttribute("msg", "用户名不能为空");
return "login";
}
if (!StringUtils.hasLength(password)) {
model.addAttribute("msg", "密码不能为空");
return "login";
}
// 获取当前用户主体
Subject subject = SecurityUtils.getSubject();
// 将用户名和密码封装成 UsernamePasswordToken 对象
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username.trim(), password.trim());
// 执行认证流程,走我们的自定义realm
subject.login(usernamePasswordToken);
// 登录成功,将用户信息存于session
HttpSession session = request.getSession();
// 这里获取的载荷是realm中决定的
User user = (User) subject.getPrincipal();
session.setAttribute("user", user);
return "index";
}
~~~
一个列表接口,这个接口只有admin角色才能访问,有两个用户
admin拥有admin角色,user拥有user角色,数据库sql文件中内置了
~~~java
@GetMapping("/list")
@RequiresRoles("admin")
public String list(Model model){
model.addAttribute("list", userService.list());
return "list";
}
~~~
### 2.6 统一的异常处理器
为了错误稍微的好看些,配置个异常处理器,主要处理两个异常,一个认证、一个授权,有需要可以自己加
新建了一个error页面,来显示错误
~~~java
/*
* @Project:spring-boot-shiro-demo
* @Author:cxs
* @Motto:放下杂念,只为迎接明天更好的自己
* */
@ControllerAdvice
public class ShiroExceptionHandler {
/**
* 处理用户认证时所抛出的异常
* @param model
* @param e
* @return
*/
@ExceptionHandler(value = AuthenticationException.class)
public String authExceptionHandle(Model model, AuthenticationException e){
if (e instanceof UnknownAccountException) {
// 用户名不存在会抛出这个异常
model.addAttribute("msg", "用户名不存在");
return "error";
} else if (e instanceof CredentialsException) {
// 密码验证失败会抛出这个异常
model.addAttribute("msg", "密码验证失败");
return "error";
} else {
model.addAttribute("msg", "用户名或密码错误");
return "error";
}
}
/**
* 处理授权时抛出的异常
* @param model
* @param e
* @return
* AuthorizationException 用户无权限访问资源会抛出这个异常
*/
@ExceptionHandler(value = AuthorizationException.class)
public String forbiddenExceptionHandle(Model model, AuthorizationException e){
model.addAttribute("msg", "用户权限不足,拒绝访问");
return "error";
}
}
~~~
## 三、成果展示
### 3.1 admin/admin登录

### 3.2 admin查看用户列表

### 3.3 user/user登录

### 3.4 user查看用户列表

### 3.5 用户名或密码错误

## 四、源代码地址
码云:https://gitee.com/whole-stack-of-white/shiro-spring-boot-demo