起源:juejin.cn/post/6844903887871148039

1. 前言

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

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

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

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

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

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

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. 我的项目构造

4. 后期筹备

4.1 导入Pom

Spring Boot 根底就不介绍了,举荐下这个实战教程:
https://github.com/javastacks...

<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

Spring Boot 根底就不介绍了,举荐下这个实战教程:
https://github.com/javastacks...

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

6.1.2 验证是否登录

6.1.3 测试角色权限

6.1.4 测试用户操作权限

6.2 user用户和vip用户测试略

7. 总结

本文演示了SpringBoot极简集成Shiro框架,实现了根底的身份认证和受权性能,如有有余,请多指教。

后续可扩大的性能点有:

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

近期热文举荐:

1.1,000+ 道 Java面试题及答案整顿(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.Spring Boot 2.6 正式公布,一大波新个性。。

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!