springboot整合shiro,实现用户登录认证,权限校验及rememberMe
1.数据库准备
user 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(35) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`rid` bigint DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
这里的密码是z3,经过MD5多次加盐后的结果
role 角色表
CREATE TABLE `role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`desc` varchar(20) DEFAULT NULL,
`realName` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
permissions 权限表
CREATE TABLE `permissions` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
`info` varchar(20) NOT NULL,
`desc` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
role_user 角色用户关系表
CREATE TABLE `role_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`uid` bigint NOT NULL,
`rid` bigint NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
role_ps 角色权限关系表
CREATE TABLE `role_ps` (
`id` bigint NOT NULL AUTO_INCREMENT,
`rid` bigint NOT NULL,
`pid` bigint NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
项目结构图
2.导包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
3.User实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String pwd;
private Long rid;
}
4.UserMapper类
@Repository
public interface UserMapper extends BaseMapper<User> {
// 通过用户名称获取角色名称
@Select("select r.name from role r left join role_user ru on r.id = ru.rid left join user u on ru.uid = u.id where u.name = #{name}")
List<String> getUserRoles(@Param(value = "name") String name);
// 通过角色名称获取相关权限
List<String> getPermissions(@Param(value = "roles") List<String> roles);
}
5.mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lkx.shiro.mapper.UserMapper">
<select id="getPermissions" resultType="java.lang.String">
SELECT p.info FROM permissions p LEFT JOIN role_ps rp ON p.id = rp.pid
left join role r on r.id = rp.rid
<where>
<if test="roles != null and roles.size > 0">
r.name in
<foreach collection="roles" item="role" open="(" close=")" separator=",">
#{role}
</foreach>
</if>
</where>
</select>
</mapper>
6.service接口
public interface IUserService extends IService<User>{
// 通过用户名查询用户信息
User getUserInfoByName(String name);
// 通过用户名称获取角色名称
List<String> getUserRoles(String name);
// 通过角色名称获取相关权限
List<String> getPermissions(List<String> roles);
}
7.UserServiceImpl实现类
/**
* @author likx
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public User getUserInfoByName(String name) {
LambdaQueryWrapper<User> eq = new QueryWrapper<User>().lambda().eq(User::getName, name);
return baseMapper.selectOne(eq);
}
@Override
public List<String> getUserRoles(String name) {
return this.baseMapper.getUserRoles(name);
}
@Override
public List<String> getPermissions(List<String> roles) {
return this.baseMapper.getPermissions(roles);
}
}
8.自定义realm
@Component
@Slf4j
public class MyRealm extends AuthorizingRealm {
@Autowired
protected IUserService userService;
/**
* 自定义授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("自定义授权");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 获取当前用户的角色
List<String> userRoles = userService.getUserRoles(principalCollection.getPrimaryPrincipal().toString());
log.info("roles:{}",userRoles);
if (CollectionUtils.isNotEmpty(userRoles)) {
authorizationInfo.addRoles(userRoles);
// 获取用户拥有的权限
List<String> permissions = userService.getPermissions(userRoles);
log.info("permissions:{}",permissions);
if (CollectionUtils.isNotEmpty(permissions)) {
authorizationInfo.addStringPermissions(permissions);
}
}
return authorizationInfo;
}
/**
* 自定义登录认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String name = authenticationToken.getPrincipal().toString();
User user = userService.getUserInfoByName(name);
if (Objects.nonNull(user)) {
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
user.getPwd(),
ByteSource.Util.bytes("salt"),
name);
return authenticationInfo;
}
return null;
}
}
9.shiro配置类
/**
* @author likx
*/
@Configuration
public class ShiroConfig {
@Autowired
private MyRealm myRealm;
/**
* 配置securityManage
* @return
*/
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager() {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
// 加密对象
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// md5加密
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 迭代加密次数
hashedCredentialsMatcher.setHashIterations(3);
myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
manager.setRememberMeManager(rememberMeManager());
// 使用自定义realm
manager.setRealm(myRealm);
return manager;
}
/**
* rememberCookie生成
* @return
*/
public SimpleCookie rememberCookie() {
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(30*24*60*60);
return cookie;
}
/**
* rememberMeManager 生成
* @return
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberCookie());
cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
return cookieRememberMeManager;
}
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
// 设置不认证可访问的资源
definition.addPathDefinition("/myController/userLogin","anon");
// 登录过滤
definition.addPathDefinition("/login","anon");
// 登出过滤
definition.addPathDefinition("/logout","logout");
// 设置需要登录认证的拦截范围
definition.addPathDefinition("/**","authc");
definition.addPathDefinition("/**","user");
return definition;
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
10.controller类
@Controller
@RequestMapping("/myController")
@Slf4j
public class MyController {
@GetMapping("/userLogin")
public String userLogin(@RequestParam(value = "name") String name,
@RequestParam(value = "pwd") String pwd,
@RequestParam(name = "rememberMe",defaultValue = "false") Boolean rememberMe,
HttpSession session) {
Subject subject = SecurityUtils.getSubject();
AuthenticationToken usernamePasswordToken = new UsernamePasswordToken(name, pwd,rememberMe);
try {
subject.login(usernamePasswordToken);
session.setAttribute("user",name);
log.info("登录成功");
return "index";
} catch (Exception e) {
e.printStackTrace();
log.info("登录失败");
return "登录失败";
}
}
@GetMapping("/loginRm")
public String login(HttpSession session) {
Object user = session.getAttribute("user");
log.info("记住我登录:{}",user.toString());
return "index";
}
/**
* 登录认证角色验证
*/
@RequiresRoles("admin")
@GetMapping("/userLoginRoles")
@ResponseBody
public String userLoginRoles() {
log.info("登录认证角色验证");
return "验证角色成功";
}
/**
* 登录认证权限验证
*/
@GetMapping("/userLoginPermissions")
@RequiresPermissions("user:delete")
@ResponseBody
public String userLoginPermissions() {
log.info("登录认证权限验证");
return "验证权限成功";
}
@GetMapping("/login")
public String login() {
return "login";
}
}
11.权限异常处理
@ControllerAdvice
public class PermissionsException {
@ResponseBody
@ExceptionHandler(UnauthorizedException.class)
public String unauthorizedException(Exception e) {
return "无权限";
}
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public String authorizationException(Exception e) {
return "权限认证失败";
}
}
12.项目配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
spring:
datasource:
url: jdbc:mysql://localhost:3306/shirodb?charterEncoding=utf-8&useSSL=false
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
shiro:
loginUrl: /myController/login
server:
port: 8081
13.启动类
@SpringBootApplication
@MapperScan("com.lkx.shiro.mapper")
public class ShiroApplication {
public static void main(String[] args) {
SpringApplication.run(ShiroApplication.class,args);
}
}
14.前端页面
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>Shiro登录成功</h1>
<br>
登录用户为:<span th:text="${session.user}"></span>
<br>
<a href="/logout">登出</a>
<br>
<a shiro:hasRole="admin" href="/myController/userLoginRoles">测试授权</a>
<br>
<a shiro:hasPermission="user:delete" href="/myController/userLoginPermissions">测试权限</a>
</body>
</html>
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="checkbox" name="rememberMe" value="true"></div>
<div><input type="submit" value="登录"></div>
</form>
</body>
</html>