Springboot整合Shiro
一、登录认证
1、搭建环境
下一步:
下一步:
下一步:
完成springboot项目构建
2、pom.xml导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shiro核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.9.0</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysqL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- log4j日志依赖 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
3、application.properties配置文件添加内容
#配置项目端口号
server.port=8080
#数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
shiro.loginUrl= /myController/userLogin
4、创建数据库以及表
#建库
create database springboot_shiro character set utf8mb4;
#建表
CREATE TABLE `user` (
`id` int(255) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` varchar(1000) DEFAULT NULL COMMENT '姓名',
`pwd` varchar(1000) DEFAULT NULL COMMENT '密码',
`rid` int(255) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5、添加实体类user
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
private String rid;
}
6、添加mapper接口(记得添加mapper扫描)
@Repository
public interface UserMapper extends BaseMapper<User> {
}
7、添加UserService接口
public interface UserService {
//用户登录
User getUserInfoByName(String name);
}
8、实现UserService接口
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public User getUserInfoByName(String name) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name",name);
User user = userMapper.selectOne(queryWrapper);
return user;
}
}
9、自定义一个登录认证MyRealm
/*
* 自定义登录认证
* */
@Component
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//登录认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1、获取用户信息
String name = authenticationToken.getPrincipal().toString();
//2、调用业务层获取数据库用户信息
User user = userService.getUserInfoByName(name);
//3、非空判断,封装返回数据
if (user!=null){
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
user.getName(),
//md5加的盐
ByteSource.Util.bytes("cdy"),
authenticationToken.getPrincipal().toString()
);
return info;
}
return null;
}
}
10、创建Shiro核心类ShiroConfig
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
//1、创建defaultWebSecurityManager对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2、创建加密对象
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//2.1采用md5加密
matcher.setHashAlgorithmName("md5");
//2.2迭代加密次数
matcher.setHashIterations(3);
//3、将加密对象存储到myRealm中
myRealm.setCredentialsMatcher(matcher);
//4、将myRealm存入defaultWebSecurityManager对象中
defaultWebSecurityManager.setRealm(myRealm);
//5、返回
return defaultWebSecurityManager;
}
//配置Shiro内置过滤器拦截范围
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
definition.addPathDefinition("/myController/userLogin", "anon");
//设置需要进行登录认证的拦截范国
definition.addPathDefinition("/**", "authc");
return definition;
}
}
11、实现访问接口controller
@Controller
@RequestMapping("/myController")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/userLogin")
@ResponseBody
public String userLogin(String name , String pwd){
System.out.println(name);
System.out.println(pwd);
//1、获取Subject对象
Subject subject = SecurityUtils.getSubject();
//2、获取token对象:用户名及密码
AuthenticationToken token = new UsernamePasswordToken(name,pwd);
//4、完成登录
try{
subject.login(token);
System.out.println("登录成功");
return "登录成功";
} catch(UnknownAccountException e ){
e.printStackTrace();
System.out.println("用户不存在");
return "用户不存在";
}catch (IncorrectCredentialsException e ){
e.printStackTrace();
System.out.println("密码错误");
return "密码错误";
}
catch (AuthenticationException e){
e.printStackTrace();
return "登录失败";
}
}
}
12、添加登录相关前端页面
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>Shiro登录认证</h1></br>
<form action="/myController/userLogin">
<div>用户名:<input type="text" name="name" value=""></div>
<div>密码:<input type="password" name="pwd" value=""></div>
<div> <input type="submit" value="登录"></div>
</form>
</body>
</html>
main.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>主页</title>
</head>
<body>
<h1>Shiro登录主页</h1><br>
<h2>登录用户为:</h2><span th:text="${session.user}"></span>
</body>
</html>
error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面出错</title>
</head>
<body>
<h1>页面出错</h1>
</body>
</html>
13、改造UserController
@Controller
@RequestMapping("/myController")
public class UserController {
@Autowired
private UserService userService;
/*
* 登录跳转页面
* */
@GetMapping("login")
public String login(){
return "login";
}
/*登录认证*/
@RequestMapping("/userLogin")
public String userLogin(String name , String pwd,HttpSession httpSession ){
System.out.println(name);
System.out.println(pwd);
//1、获取Subject对象
Subject subject = SecurityUtils.getSubject();
//2、获取token对象:用户名及密码
AuthenticationToken token = new UsernamePasswordToken(name,pwd);
//4、完成登录
try{
subject.login(token);
httpSession.setAttribute("user",token.getPrincipal().toString());
System.out.println("登录成功");
return "main";
} catch(UnknownAccountException e ){
e.printStackTrace();
System.out.println("用户不存在");
return "error";
}catch (IncorrectCredentialsException e ){
e.printStackTrace();
System.out.println("密码错误");
return "error";
}
catch (AuthenticationException e){
e.printStackTrace();
return "error";
}
}
}
注意:
二、授权角色验证
1、授权
用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判 断。 这个工具就是Realm的doGetAuthorizationInfo方法进行判断。
触发权限判断的有两种 方式
(1) 在页面中通过shiro************属性判断
(2) 在接口服务中通过注解@Requires************进行判断
2、后端接口服务注解
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加 在业务方法上,一般加在控制器方法上。常用注解如下:
(1)@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated()
(2)@RequiresUser
验证用户是否被记忆: 登录认证成功subject.isAuthenticated()为true 登录后被记忆subject.isRemembered()为true
(3)@RequiresGuest
验证是否是一个guest的请求,是否是游客的请求 此时subject.getPrincipal()为null
(4)@RequiresRoles
验证subject是否有相应角色,有角色访问方法,没有则会抛出异常 AuthorizationException。
(5)@RequiresPermissions
验证subject是否有相应权限,有权限访问方法,没有则会抛出异常 AuthorizationException。
3、创建角色(role)表
CREATE TABLE `role` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '角色名',
`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
`realname` VARCHAR(20) DEFAULT NULL COMMENT '角色显示名',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表';
4、创建中间表(role_user)
CREATE TABLE `role_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`uid` BIGINT(20) DEFAULT NULL COMMENT '用户 id',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用户映射表';
5、修改 MyRealm 方法
//自定义授权方法:获取当前登录用户权限信息,返回给 Shiro 用来进行授权对比
@Override
protected AuthorizationInfo
doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("进入自定义授权方法");
//获取当前用户身份信息
String principal =
principalCollection.getPrimaryPrincipal().toString();
//调用接口方法获取用户的角色信息
List<String> roles = userService.getUserRoleInfo(principal);
System.out.println("当前用户角色信息:"+roles);
//创建对象,存储当前登录的用户的权限和角色
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//存储角色
info.addRoles(roles);
//返回
return info;
}
6、添加 UserController 方法,并添加验证角色注解
//登录认证验证角色
@RequiresRoles("admin")
@GetMapping("userLoginRoles")
@ResponseBody
public String userLoginRoles() {
System.out.println("登录认证验证角色");
return "验证角色成功";
}
7、main.html页面添加
<br>
<a href="/myController/userLoginRoles">测试授权</a>
8、UserMapper添加方法
@Repository
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM
role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
List<String> getUserRoleInfoMapper(@Param("principal") String
principal);
}
9、UserService添加接口方法
//获取用户的角色信息
List<String> getUserRoleInfo(String principal);
10、UserServiceImpl 添加
//获取用户的角色信息
@Override
public List<String> getUserRoleInfo(String principal) {
return userMapper.getUserRoleInfoMapper(principal);
}
三、授权权限验证
1、创建权限表
CREATE TABLE `permissions` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '权限名',
`info` VARCHAR(30) DEFAULT NULL COMMENT '权限信息',
`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='权限表';
2、创建角色权限表
CREATE TABLE `role_ps` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
`pid` BIGINT(20) DEFAULT NULL COMMENT '权限 id',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色权限映射表'
3、UserMapper添加方法
@Select({
"<script>",
"select info FROM permissions WHERE id IN ",
"(SELECT pid FROM role_ps WHERE rid IN (",
"SELECT id FROM role WHERE NAME IN ",
"<foreach collection='roles' item='name' open='('
separator=',' close=')'>",
"#{name}",
"</foreach>",
"))",
"</script>"
})
List<String> getUserPermissionInfoMapper(@Param("roles")List<String>
roles);
4、UserService添加接口方法
//获取用户角色的权限信息
List<String> getUserPermissionInfo(List<String> roles);
5、UserServiceImpl 添加
//获取用户角色的权限信息
@Override
public List<String> getUserPermissionInfo(List<String> roles) {
return userMapper.getUserPermissionInfoMapper(roles);
}
6、修改 MyRealm 方法
//自定义授权方法:获取当前登录用户权限信息,返回给 Shiro 用来进行授权对比
@Override
protected AuthorizationInfo
doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("进入自定义授权方法");
//获取当前用户身份信息
String principal =
principalCollection.getPrimaryPrincipal().toString();
//调用接口方法获取用户的角色信息
List<String> roles = userService.getUserRoleInfo(principal);
System.out.println("当前用户角色信息:"+roles);
//调用接口方法获取用户角色的权限信息
List<String> permissions =
userService.getUserPermissionInfo(roles);
System.out.println("当前用户权限信息:"+permissions);
//创建对象,存储当前登录的用户的权限和角色
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//存储角色
info.addRoles(roles);
//存储权限信息
info.addStringPermissions(permissions);
//返回
return info;
}
7、添加 UserController 方法
//登录认证验证权限
@RequiresPermissions("user:delete")
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions() {
System.out.println("登录认证验证权限");
return "验证权限成功";
}
8、main.html页面添加
<br>
<a href="/myController/userPermissions">测试授权-权限验证</a>
四、授权验证异常处理
创建异常类
@ControllerAdvice
public class PermissionsException {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String unauthorizedException(Exception ex){
return "无权限";
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String authorizationException(Exception ex){
return "权限认证失败";
}