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!