Spring Boot integrated Shiro minimalist tutorial (practical version) who else won't??

Source: Juejin cn/post/6844903887871148039

1. Preface

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: that is, the current user. In the application of permission management, you often need to know who can operate what and who has the right to operate the program. shiro needs to provide basic current user information through subject. Subject not only represents a user, but anything interacting with the current application is a subject, such as web crawler. All subjects must be bound to the SecurityManager, and the interaction with the subject is actually transformed into the interaction with the SecurityManager.
  • SecurityManager: the manager of all subjects, which is the core component of Shiro framework. It can be regarded as a global management component of Shiro framework, which is used to schedule various Shiro framework services. Similar to the dispatcher servlet in spring MVC, it is used to intercept and process all requests.
  • Realm: realm is the user's information authenticator and the user's authority witness. We need to implement the self-defined authority rules of realm to manage our own system. To authenticate users, the SecurityManager needs to obtain users from the 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 users', 'user');
INSERT INTO `user` VALUES (3, 'vip', 'VIP user', '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', 'Super administrator');
INSERT INTO `role` VALUES (2, 'user', 'Ordinary users');
INSERT INTO `role` VALUES (3, 'vip_user', 'VIP user');

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 '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', 'increase');
INSERT INTO `permission` VALUES (2, 'update', 'to update');
INSERT INTO `permission` VALUES (3, 'select', 'see');
INSERT INTO `permission` VALUES (4, 'delete', 'delete');

SET FOREIGN_KEY_CHECKS = 1;

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;

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;

3. Project structure

4. Preliminary preparation

4.1 import Pom

I won't introduce the basics of Spring Boot. I recommend this practical tutorial:
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: 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 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;
}

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;
}

4.4 Dao layer

4.4.1 PermissionMapper.java

@Mapper
@Repository
public 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
@Repository
public 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
@Repository
public 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 layer

4.5.1 PermissionServiceImpl.java

@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

@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

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

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

4.6. System return status enumeration and wrapper function

4.6.1 ServerResponseEnum.java

@AllArgsConstructor
@Getter
public enum ServerResponseEnum {
    SUCCESS(0, "success"),
    ERROR(10, "fail"),

    ACCOUNT_NOT_EXIST(11, "Account does not exist"),
    DUPLICATE_ACCOUNT(12, "Duplicate account"),
    ACCOUNT_IS_DISABLED(13, "Account disabled"),
    INCORRECT_CREDENTIALS(14, "Wrong account or password"),
    NOT_LOGIN_IN(15, "Account not logged in"),
    UNAUTHORIZED(16, "No permission")
    ;
    Integer code;
    String message;
}

4.6.2 ServerResponseVO.java

@Getter
@Setter
@NoArgsConstructor
public class ServerResponseVO<T> implements Serializable {
    private static final long serialVersionUID = -1005863670741860901L;
    // Response code
    private Integer code;

    // Description information
    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;
    }

    /**
     * Return success information
     * @param data      information content
     * @param <T>
     * @return
     */
    public static<T> ServerResponseVO success(T data) {
        return new ServerResponseVO<>(ServerResponseEnum.SUCCESS, data);
    }

    /**
     * Return success information
     * @return
     */
    public static ServerResponseVO success() {
        return new ServerResponseVO(ServerResponseEnum.SUCCESS);
    }

    /**
     * Return error message
     * @param responseCode      Response code
     * @return
     */
    public static ServerResponseVO error(ServerResponseEnum responseCode) {
        return new ServerResponseVO(responseCode);
    }
}

4.7 unified exception handling

When user authentication fails, unauthorized exception will be thrown. We can handle this exception through unified exception handling

@RestControllerAdvice
public class UserExceptionHandler {

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

5. Integrated Shiro

5.1 UserRealm.java

I won't introduce the basics of Spring Boot. I recommend this practical tutorial:
https://github.com/javastacks...

/**
 * Responsible for authenticating user identity 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());
        }
        // Put in role information
        authorizationInfo.setRoles(roleSet);
        // Put in 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 new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
}

5.2 ShiroConfig.java

@Configuration
public class ShiroConfig {

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

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

    /**
     * Path filtering rules
     * @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<>();
        // In order
        map.put("/login", "anon");      // Allow anonymous access
        map.put("/**", "authc");        // Access is only possible after authentication
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * Enable Shiro annotation mode to add annotations to the methods in the 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 {
            // validate logon
            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 "Successfully logged in";
    }

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

    @GetMapping("/permission")
    @RequiresPermissions(value = {"add", "update"}, logical = Logical.AND)
    public String permission() {
        return "test Add and Update jurisdiction";
    }
}

6. Test

6.1 log in as root

6.1.1 login

6.1.2 verify whether to log in

6.1.3 test role permissions

6.1.4 test user operation authority

6.2 user and vip user testing

7. Summary

This article demonstrates the spring boot minimalist integrated Shiro framework, which realizes the basic identity authentication and authorization functions. If there are deficiencies, please give more advice.

The following extensible function points are:

  • Integrating Redis to realize Shiro's distributed session
  • Integrate JWT to realize single sign on function

Recent hot article recommendations:

1.1000 + Java interview questions and answers (2022 latest version)

2.Hot! The Java collaboration is coming...

3.Spring Boot 2.x tutorial, too complete!

4.Spring Boot 2.6 was officially released, a wave of new features..

5.Java development manual (Songshan version) is the latest release. Download it quickly!

Feel good, don't forget to like + forward!

Keywords: Java

Added by genie on Wed, 26 Jan 2022 22:41:41 +0200