1. Introduction

Apache Shiro is a powerful and easy-to-use Java security framework that provides authentication, authorization, encryption, and session management.

Shiro has three core components:

Subject: In the application of permission management, the current user often needs to know who can operate what and who has the right to operate the program. In Shiro, the basic information of the current user needs to be provided by Subject. Subject not only represents a user, but anything that interacts with the current application is Subject. Such as web crawlers. All subjects are bound to the SecurityManager, and interactions with the Subject are actually translated into interactions with the SecurityManager.

SecurityManager: The manager of all Subjects. This is the core component of Shiro framework. It can be regarded as a global management component of Shiro framework for scheduling various Shiro framework services. Similar to the DispatcherServlet in SpringMVC, it intercepts all requests and processes them.

Realm: Realm is the user’s information authenticator and user’s permission authenticator. We need to implement Realm’s custom permission rules that govern our own systems. To authenticate the user, the SecurityManager needs to fetch the user from a Realm. You can think of Realm as a data source.

2. Database design

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'.'Super User'.'root');
INSERT INTO `user` VALUES (2.'user'.'Ordinary user'.'user');
INSERT INTO `user` VALUES (3.'vip'.'the VIP users'.'vip');

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

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'.'Super Administrator');
INSERT INTO `role` VALUES (2.'user'.'Ordinary user');
INSERT INTO `role` VALUES (3.'vip_user'.'the VIP users');

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

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 'Permission Name'.`desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'Permission Description',
  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'.'add');
INSERT INTO `permission` VALUES (2.'update'.'update');
INSERT INTO `permission` VALUES (3.'select'.'look at');
INSERT INTO `permission` VALUES (4.'delete'.'delete');

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

2.4 User_Role(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;
Copy the code

2.5 Role_Permission(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;

Copy the code

3. Project structure

4. Prepare

4.1 the import 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>
Copy the code

4.2 application. Yml

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

Copy the code

4.3 entity class

4.3.1 User. Java
@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;
}
Copy the code
4.3.2 Role. Java
@Data
@ToString
public class Role implements Serializable {

    private static final long serialVersionUID = -1767327914553823741L;

    private Integer id;

    private String role;

    private String desc;
}

Copy the code

4.4 the Dao layer

4.4.1 PermissionMapper. Java
@Mapper
@Repository
public interface PermissionMapper {

    List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);
}
Copy the code
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>
Copy the code
4.4.3 RoleMapper. Java
@Mapper
@Repository
public interface RoleMapper {

    List<Role> findRoleByUserId(@Param("userId") Integer userId);
}
Copy the code
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>
Copy the code
4.4.5 UserMapper. Java
@Mapper
@Repository
public interface UserMapper {
    User findByAccount(@Param("account") String account);
}
Copy the code
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>
Copy the code

4.5 the Service layer

4.5.1 PermissionServiceImpl. Java
@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List<String> findByRoleId(List<Integer> roleIds) {
        returnpermissionMapper.findByRoleId(roleIds); }}Copy the code
4.5.2 RoleServiceImpl. Java
@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public List<Role> findRoleByUserId(Integer id) {
        returnroleMapper.findRoleByUserId(id); }}Copy the code
4.5.3 UserServiceImpl. Java
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User findByAccount(String account) {
        returnuserMapper.findByAccount(account); }}Copy the code

4.6. The system returns state enumeration and wrapping functions

4.6.1 ServerResponseEnum. Java
@AllArgsConstructor
@Getter
public enum ServerResponseEnum {
    SUCCESS(0."Success"),
    ERROR(10."Failure"),

    ACCOUNT_NOT_EXIST(11."Account does not exist"),
    DUPLICATE_ACCOUNT(12."Duplicate account number"),
    ACCOUNT_IS_DISABLED(13."Account is disabled"),
    INCORRECT_CREDENTIALS(14."Wrong account or password"),
    NOT_LOGIN_IN(15."Account not logged in"),
    UNAUTHORIZED(16."No access"); Integer code; String message; }Copy the code
4.6.2 ServerResponseVO. Java
@Getter
@Setter
@NoArgsConstructor
public class ServerResponseVO<T> implements Serializable {
    private static final long serialVersionUID = -1005863670741860901L;
    / / the response code
    private Integer code;

    // Description
    private String message;

    // Response content
    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;
    }

    /** * Returns a success message *@paramData Information content *@param <T>
     * @return* /
    public static<T> ServerResponseVO success(T data) {
        return new ServerResponseVO<>(ServerResponseEnum.SUCCESS, data);
    }

    /** * Returns a success message *@return* /
    public static ServerResponseVO success(a) {
        return new ServerResponseVO(ServerResponseEnum.SUCCESS);
    }

    /** * Returns an error message *@paramResponseCode responseCode *@return* /
    public static ServerResponseVO error(ServerResponseEnum responseCode) {
        return newServerResponseVO(responseCode); }}Copy the code

4.7 Unified Exception Handling

When user authentication fails, an UnauthorizedException is thrown, which can be handled by unified exception handling

@RestControllerAdvice
public class UserExceptionHandler {

    @ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ServerResponseVO UnAuthorizedExceptionHandler(UnauthorizedException e) {
        returnServerResponseVO.error(ServerResponseEnum.UNAUTHORIZED); }}Copy the code

5. Integrated Shiro

5.1 UserRealm. Java

/** * Is responsible for authenticating and authorizing users */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    // User authorization
    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());
        }
        // Add role information
        authorizationInfo.setRoles(roleSet);
        // Add permission information
        List<String> permissionList = permissionService.findByRoleId(roleIds);
        authorizationInfo.setStringPermissions(new HashSet<>(permissionList));

        return authorizationInfo;
    }

    // User authentication
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authToken;
        User user = userService.findByAccount(token.getUsername());
        if (user == null) {
            return null;
        }
        return newSimpleAuthenticationInfo(user, user.getPassword(), getName()); }}Copy the code

5.2 ShiroConfig. Java

@Configuration
public class ShiroConfig {

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

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

    /** * Path filtering rule *@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<>();
        // There is a sequence
        map.put("/login"."anon");      // Allow anonymous access
        map.put("/ * *"."authc");        // Authentication is required before access
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /** * Enable Shiro annotation mode to add annotations to methods in Controller *@param securityManager
     * @return* /
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
Copy the code

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 {
            // Login authentication
            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();
            returnServerResponseVO.error(ServerResponseEnum.ERROR); }}@GetMapping("/login")
    public ServerResponseVO login(a) {
        return ServerResponseVO.error(ServerResponseEnum.NOT_LOGIN_IN);
    }

    @GetMapping("/auth")
    public String auth(a) {
        return "Logged in successfully";
    }

    @GetMapping("/role")
    @RequiresRoles("vip")
    public String role(a) {
        return "Test THE Vip role";
    }

    @GetMapping("/permission")
    @RequiresPermissions(value = {"add"."update"}, logical = Logical.AND)
    public String permission(a) {
        return "Test Add and Update permissions"; }}Copy the code

Test 6.

6.1 Login as the root user

6.1.1 login

6.1.2 Verifying login

6.1.3 Testing Role Rights

6.1.4 Testing user Operation Rights

6.2 User and VIP User Tests are omitted

7. To summarize

This article demonstrates the SpringBoot minimalist integration Shiro framework, to achieve the basic identity authentication and authorization functions, if there is any deficiency, please kindly advise.

Subsequent extendable feature points are:

  1. Integrate Redis to implement Shiro’s distributed session
  2. Integrate JWT for single sign-on functionality