前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot 集成 Shiro 极简教程(实战版)

SpringBoot 集成 Shiro 极简教程(实战版)

作者头像
Java小咖秀
修改2021-04-20 10:21:18
2.8K0
修改2021-04-20 10:21:18
举报
文章被收录于专栏:Java冰冻三尺

来源:juejin.cn/post/6844903887871148039

1. 前言

Apache Shiro 是一个功能强大且易于使用的 Java 安全框架,提供了认证,授权,加密,和会话管理。

img
img

Shiro 有三大核心组件:

  • Subject: 即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro 中则需要通过 Subject 来提供基础的当前用户信息,Subject 不仅仅代表某个用户,与当前应用交互的任何东西都是 Subject,如网络爬虫等。所有的 Subject 都要绑定到 SecurityManager 上,与 Subject 的交互实际上是被转换为与 SecurityManager 的交互。
  • SecurityManager: 即所有 Subject 的管理者,这是 Shiro 框架的核心组件,可以把他看做是一个 Shiro 框架的全局管理组件,用于调度各种 Shiro 框架的服务。作用类似于 SpringMVC 中的 DispatcherServlet,用于拦截所有请求并进行处理。
  • Realm: Realm 是用户的信息认证器和用户的权限人证器,我们需要自己来实现 Realm 来自定义的管理我们自己系统内部的权限规则。SecurityManager 要验证用户,需要从 Realm 中获取用户。可以把 Realm 看做是数据源。

2. 数据库设计

2.1 User(用户)

img
img
代码语言:javascript
复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '超级用户', 'root');
INSERT INTO `user` VALUES (2, 'user', '普通用户', 'user');
INSERT INTO `user` VALUES (3, 'vip', 'VIP用户', 'vip');
​
SET FOREIGN_KEY_CHECKS = 1;

2.2 Role(角色)

img
img
代码语言:javascript
复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'admin', '超级管理员');
INSERT INTO `role` VALUES (2, 'user', '普通用户');
INSERT INTO `role` VALUES (3, 'vip_user', 'VIP用户');
​
SET FOREIGN_KEY_CHECKS = 1;

2.3 Permission(权限)

img
img
代码语言:javascript
复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `permission` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
  `desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, 'add', '增加');
INSERT INTO `permission` VALUES (2, 'update', '更新');
INSERT INTO `permission` VALUES (3, 'select', '查看');
INSERT INTO `permission` VALUES (4, 'delete', '删除');
​
SET FOREIGN_KEY_CHECKS = 1;

2.4 User_Role(用户 - 角色)

img
img
代码语言:javascript
复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Fixed;
​
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 2, 2);
INSERT INTO `user_role` VALUES (3, 3, 3);
​
SET FOREIGN_KEY_CHECKS = 1;

2.5 Role_Permission(角色 - 权限)

img
img
代码语言:javascript
复制
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NULL DEFAULT NULL,
  `permission_id` int(255) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Fixed;
​
-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 1, 2);
INSERT INTO `role_permission` VALUES (3, 1, 3);
INSERT INTO `role_permission` VALUES (4, 1, 4);
INSERT INTO `role_permission` VALUES (5, 2, 3);
INSERT INTO `role_permission` VALUES (6, 3, 3);
INSERT INTO `role_permission` VALUES (7, 3, 2);
INSERT INTO `role_permission` VALUES (8, 2, 1);
​
SET FOREIGN_KEY_CHECKS = 1;

3. 项目结构

img
img

4. 前期准备

4.1 导入 Pom

代码语言:javascript
复制
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
</dependency>
​
<dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
</dependency>
​
<dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
</dependency>
​
<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
</dependency>

4.2 application.yml

代码语言:javascript
复制
server:
  port: 8903
spring:
  application:
    name: lab-user
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/laboratory?charset=utf8
    username: root
    password: root
mybatis:
  type-aliases-package: cn.ntshare.laboratory.entity
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true

4.3 实体类

4.3.1 User.java
代码语言:javascript
复制
@Data
@ToString
public class User implements Serializable {
​
    private static final long serialVersionUID = -6056125703075132981L;
​
    private Integer id;
​
    private String account;
​
    private String password;
​
    private String username;
}
4.3.2 Role.java
代码语言:javascript
复制
@Data
@ToString
public class Role implements Serializable {

    private static final long serialVersionUID = -1767327914553823741L;

    private Integer id;

    private String role;

    private String desc;
}

4.4 Dao 层

4.4.1 PermissionMapper.java
代码语言:javascript
复制
@Mapper
@Repository
public interface PermissionMapper {

    List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);
}
4.4.2 PermissionMapper.xml
代码语言:javascript
复制
<mapper namespace="cn.ntshare.laboratory.dao.PermissionMapper">
    <sql id="base_column_list">
        id, permission, desc
    </sql>

    <select id="findByRoleId" parameterType="List" resultType="String">
        select permission
        from permission, role_permission rp
        where rp.permission_id = permission.id and rp.role_id in
        <foreach collection="roleIds" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </select>
</mapper>
4.4.3 RoleMapper.java
代码语言:javascript
复制
@Mapper
@Repository
public interface RoleMapper {

    List<Role> findRoleByUserId(@Param("userId") Integer userId);
}
4.4.4 RoleMapper.xml
代码语言:javascript
复制
<mapper namespace="cn.ntshare.laboratory.dao.RoleMapper">
    <sql id="base_column_list">
        id, user_id, role_id
    </sql>

    <select id="findRoleByUserId" parameterType="Integer" resultType="Role">
        select role.id, role
        from role, user, user_role ur
        where role.id = ur.role_id and ur.user_id = user.id and user.id = #{userId}
    </select>
</mapper>
4.4.5 UserMapper.java
代码语言:javascript
复制
@Mapper
@Repository
public interface UserMapper {
    User findByAccount(@Param("account") String account);
}
4.4.6 UserMapper.xml
代码语言:javascript
复制
<mapper namespace="cn.ntshare.laboratory.dao.UserMapper">

    <sql id="base_column_list">
        id, account, password, username
    </sql>

    <select id="findByAccount" parameterType="Map" resultType="User">
        select
        <include refid="base_column_list"/>
        from user
        where account = #{account}
    </select>
</mapper>

4.5 Service 层

4.5.1 PermissionServiceImpl.java
代码语言:javascript
复制
@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List<String> findByRoleId(List<Integer> roleIds) {
        return permissionMapper.findByRoleId(roleIds);
    }
}
4.5.2 RoleServiceImpl.java
代码语言:javascript
复制
@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public List<Role> findRoleByUserId(Integer id) {
        return roleMapper.findRoleByUserId(id);
    }
}
4.5.3 UserServiceImpl.java
代码语言:javascript
复制
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User findByAccount(String account) {
        return userMapper.findByAccount(account);
    }
}

4.6. 系统返回状态枚举与包装函数

4.6.1 ServerResponseEnum.java
代码语言:javascript
复制
@AllArgsConstructor
@Getter
public enum ServerResponseEnum {
    SUCCESS(0, "成功"),
    ERROR(10, "失败"),

    ACCOUNT_NOT_EXIST(11, "账号不存在"),
    DUPLICATE_ACCOUNT(12, "账号重复"),
    ACCOUNT_IS_DISABLED(13, "账号被禁用"),
    INCORRECT_CREDENTIALS(14, "账号或密码错误"),
    NOT_LOGIN_IN(15, "账号未登录"),
    UNAUTHORIZED(16, "没有权限")
    ;
    Integer code;
    String message;
}
4.6.2 ServerResponseVO.java
代码语言:javascript
复制
@Getter
@Setter
@NoArgsConstructor
public class ServerResponseVO<T> implements Serializable {
    private static final long serialVersionUID = -1005863670741860901L;
    // 响应码
    private Integer code;

    // 描述信息
    private String message;

    // 响应内容
    private T data;

    private ServerResponseVO(ServerResponseEnum responseCode) {
        this.code = responseCode.getCode();
        this.message = responseCode.getMessage();
    }

    private ServerResponseVO(ServerResponseEnum responseCode, T data) {
        this.code = responseCode.getCode();
        this.message = responseCode.getMessage();
        this.data = data;
    }

    private ServerResponseVO(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 返回成功信息
     * @param data      信息内容
     * @param <T>
     * @return
     */
    public static<T> ServerResponseVO success(T data) {
        return new ServerResponseVO<>(ServerResponseEnum.SUCCESS, data);
    }

    /**
     * 返回成功信息
     * @return
     */
    public static ServerResponseVO success() {
        return new ServerResponseVO(ServerResponseEnum.SUCCESS);
    }

    /**
     * 返回错误信息
     * @param responseCode      响应码
     * @return
     */
    public static ServerResponseVO error(ServerResponseEnum responseCode) {
        return new ServerResponseVO(responseCode);
    }
}

4.7 统一异常处理

当用户身份认证失败时,会抛出 UnauthorizedException,我们可以通过统一异常处理来处理该异常

代码语言:javascript
复制
@RestControllerAdvice
public class UserExceptionHandler {

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ServerResponseVO UnAuthorizedExceptionHandler(UnauthorizedException e) {
        return ServerResponseVO.error(ServerResponseEnum.UNAUTHORIZED);
    }
}

5. 集成 Shiro

5.1 UserRealm.java

代码语言:javascript
复制
/**
 * 负责认证用户身份和对用户进行授权
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    // 用户授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        List<Role> roleList = roleService.findRoleByUserId(user.getId());
        Set<String> roleSet = new HashSet<>();
        List<Integer> roleIds = new ArrayList<>();
        for (Role role : roleList) {
            roleSet.add(role.getRole());
            roleIds.add(role.getId());
        }
        // 放入角色信息
        authorizationInfo.setRoles(roleSet);
        // 放入权限信息
        List<String> permissionList = permissionService.findByRoleId(roleIds);
        authorizationInfo.setStringPermissions(new HashSet<>(permissionList));

        return authorizationInfo;
    }

    // 用户认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authToken;
        User user = userService.findByAccount(token.getUsername());
        if (user == null) {
            return null;
        }
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
}

5.2 ShiroConfig.java

代码语言:javascript
复制
@Configuration
public class ShiroConfig {

    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm());
        return securityManager;
    }

    /**
     * 路径过滤规则
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        Map<String, String> map = new LinkedHashMap<>();
        // 有先后顺序
        map.put("/login", "anon");      // 允许匿名访问
        map.put("/**", "authc");        // 进行身份认证后才能访问
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 开启Shiro注解模式,可以在Controller中的方法上添加注解
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

5.3 LoginController.java

代码语言:javascript
复制
@RestController
@RequestMapping("")
public class LoginController {

    @PostMapping("/login")
    public ServerResponseVO login(@RequestParam(value = "account") String account,
                                  @RequestParam(value = "password") String password) {
        Subject userSubject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(account, password);
        try {
            // 登录验证
            userSubject.login(token);
            return ServerResponseVO.success();
        } catch (UnknownAccountException e) {
            return ServerResponseVO.error(ServerResponseEnum.ACCOUNT_NOT_EXIST);
        } catch (DisabledAccountException e) {
            return ServerResponseVO.error(ServerResponseEnum.ACCOUNT_IS_DISABLED);
        } catch (IncorrectCredentialsException e) {
            return ServerResponseVO.error(ServerResponseEnum.INCORRECT_CREDENTIALS);
        } catch (Throwable e) {
            e.printStackTrace();
            return ServerResponseVO.error(ServerResponseEnum.ERROR);
        }
    }

    @GetMapping("/login")
    public ServerResponseVO login() {
        return ServerResponseVO.error(ServerResponseEnum.NOT_LOGIN_IN);
    }

    @GetMapping("/auth")
    public String auth() {
        return "已成功登录";
    }

    @GetMapping("/role")
    @RequiresRoles("vip")
    public String role() {
        return "测试Vip角色";
    }

    @GetMapping("/permission")
    @RequiresPermissions(value = {"add", "update"}, logical = Logical.AND)
    public String permission() {
        return "测试Add和Update权限";
    }
}

6. 测试

6.1 用 root 用户登录

6.1.1 登录
img
img
6.1.2 验证是否登录
img
img
6.1.3 测试角色权限
img
img
6.1.4 测试用户操作权限
img
img
6.2 user 用户和 vip 用户测试略

7. 总结

本文演示了 SpringBoot 极简集成 Shiro 框架,实现了基础的身份认证和授权功能,如有不足,请多指教。

后续可扩展的功能点有:

  • 集成 Redis 实现 Shiro 的分布式会话
  • 集成 JWT 实现单点登录功能

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. 数据库设计
    • 2.1 User(用户)
      • 2.2 Role(角色)
        • 2.3 Permission(权限)
          • 2.4 User_Role(用户 - 角色)
            • 2.5 Role_Permission(角色 - 权限)
            • 3. 项目结构
            • 4. 前期准备
              • 4.1 导入 Pom
                • 4.2 application.yml
                  • 4.3 实体类
                    • 4.3.1 User.java
                    • 4.3.2 Role.java
                  • 4.4 Dao 层
                    • 4.4.1 PermissionMapper.java
                    • 4.4.2 PermissionMapper.xml
                    • 4.4.3 RoleMapper.java
                    • 4.4.4 RoleMapper.xml
                    • 4.4.5 UserMapper.java
                    • 4.4.6 UserMapper.xml
                  • 4.5 Service 层
                    • 4.5.1 PermissionServiceImpl.java
                    • 4.5.2 RoleServiceImpl.java
                    • 4.5.3 UserServiceImpl.java
                  • 4.6. 系统返回状态枚举与包装函数
                    • 4.6.1 ServerResponseEnum.java
                    • 4.6.2 ServerResponseVO.java
                  • 4.7 统一异常处理
                  • 5. 集成 Shiro
                    • 5.1 UserRealm.java
                      • 5.2 ShiroConfig.java
                        • 5.3 LoginController.java
                        • 6. 测试
                          • 6.1 用 root 用户登录
                            • 6.1.1 登录
                            • 6.1.2 验证是否登录
                            • 6.1.3 测试角色权限
                            • 6.1.4 测试用户操作权限
                            • 6.2 user 用户和 vip 用户测试略
                        • 7. 总结
                        相关产品与服务
                        云数据库 Redis
                        腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档