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

沙海
沙海
沙海
1017
文章
2
评论
2021年4月20日05:53:32
评论
0 16245字阅读54分9秒
摘要

速读摘要

速读摘要

Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证,和会话管理。不仅仅代表某个用户,与当前应用交互的任何东西都是Subject,如网络爬虫等。即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。Realm是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realm来自定义的管理我们自己系统内部的权限规则。

原文约 2962 | 图片 14 | 建议阅读 6 分钟 | 评价反馈

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

点击关注 ? 小哈学Java

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

来源:juejin.cn/post/6844903887871148039

1. 前言

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

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

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(用户)

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

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(角色)

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

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(权限)

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

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(用户-角色)

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

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(角色-权限)

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

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. 项目结构

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

4. 前期准备

4.1 导入Pom

<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

server:  port: 8903spring:  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: rootmybatis:  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

@Data@ToStringpublic 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

@Data@ToStringpublic 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

@Mapper@Repositorypublic interface PermissionMapper {    List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);}

4.4.2 PermissionMapper.xml

<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

@Mapper@Repositorypublic interface RoleMapper {    List<Role> findRoleByUserId(@Param("userId") Integer userId);}

4.4.4 RoleMapper.xml

<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

@Mapper@Repositorypublic interface UserMapper {    User findByAccount(@Param("account") String account);}

4.4.6 UserMapper.xml

<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

@Servicepublic 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

@Servicepublic class RoleServiceImpl implements RoleService {    @Autowired    private RoleMapper roleMapper;    @Override    public List<Role> findRoleByUserId(Integer id) {        return roleMapper.findRoleByUserId(id);    }}

4.5.3 UserServiceImpl.java

@Servicepublic 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

@AllArgsConstructor@Getterpublic 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

@Getter@Setter@NoArgsConstructorpublic 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,我们可以通过统一异常处理来处理该异常

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

5. 集成Shiro

5.1 UserRealm.java

/** * 负责认证用户身份和对用户进行授权 */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

@Configurationpublic 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

@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 登录

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

6.1.2 验证是否登录

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

6.1.3 测试角色权限

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

6.1.4 测试用户操作权限

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

6.2 user用户和vip用户测试略

7. 总结

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

后续可扩展的功能点有:

  • 集成Redis实现Shiro的分布式会话

  • 集成JWT实现单点登录功能

1. 以为精通Java 线程池,看到这些误区,还是年轻了...2. Spring Boot 集成 WebSocket,轻松实现信息推送!3. try-catch-finally 和 return 是怎么执行的?4. 在 SpringBoot 项目中,Spring Security 和 Shiro 该如何选择?最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

继续阅读
历史上的今天
4月
20
weinxin
资源分享QQ群
本站是一个IT技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: