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多次加盐后的结果
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YlUJTftm-1688696284074)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230707092440322.png)]](https://img-blog.csdnimg.cn/de49531977e14d9da94b86a9e8c2d6c1.png)
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;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L732BAfj-1688696284075)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230707094151729.png)]](https://img-blog.csdnimg.cn/31054b11370646088189013dc2a441eb.png)
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;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MLy4G4e-1688696284075)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230707094238501.png)]](https://img-blog.csdnimg.cn/46e0ee8cd99a4362b7aaa8d4efab83da.png)
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;
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aQtIoo0Z-1688696284075)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230707094316856.png)]](https://img-blog.csdnimg.cn/737f2a1cfd7e4cd6a5c8c6989ccdf44b.png)
项目结构图
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eT9Di6Pf-1688696284076)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230707094503752.png)]](https://img-blog.csdnimg.cn/0cf25f052cb34a898f6b2182e4074f4e.png)
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>



















