Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

preface

For Java Security frameworks, one is Spring Security and the other is Shiro. These two frameworks are very good, there is no absolute who is better or worse, depending on the business scenario to choose the framework, the most suitable is the best.

Today we’re going to focus on Shiro as a security framework that I’m sure you use a lot. As for why Shiro is so popular, I think it’s the simplicity and power of Shiro that has caught the attention of many developers out of all the permission frameworks.

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, password, and session management. Use Shiro’s easy-to-understand API. You can quickly and easily get any application, from the smallest mobile applications to the largest network and enterprise applications.

To sum up, Shiro is simple, flexible and easy to use. As for the basic concepts, I think it’s easy to explain them step by step in the code.

The body of the

The process of Shiro login, authentication and authorization is as follows:

Springboot integrates with Shiro framework for push-button level permissions. Involving authority, which involves three tables of users, roles and permissions and two associated tables of user roles and role permissions. The database I use is the common MYSQL, here I simply designed the table structure, as follows.

DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
  `id` int(11) NOT NULL,
  `available` int(11) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `parent_id` int(11) DEFAULT NULL,
  `parent_ids` varchar(255) DEFAULT NULL,
  `permission` varchar(255) DEFAULT NULL,
  `resource_type` varchar(255) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1'.'0'.'User Management'.'0'.'0 /'.'userInfo:view'.'menu'.'userInfo/userList');
INSERT INTO `sys_permission` VALUES ('2'.'0'.'Add user'.'1'.'0/1'.'userInfo:add'.'button'.'userInfo/userAdd');
INSERT INTO `sys_permission` VALUES ('3'.'0'.'Delete user'.'1'.'0/1'.'userInfo:del'.'button'.'userInfo/userDel');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL,
  `available` int(11) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  `role` varchar(255) DEFAULT NULL.PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1'.'0'.'Administrator'.'admin');
INSERT INTO `sys_role` VALUES ('2'.'0'.'VIP'.'vip');
INSERT INTO `sys_role` VALUES ('3'.'1'.'Tester'.'test');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
  `permission_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  KEY `FKomxrs8a388bknvhjokh440waq` (`permission_id`),
  KEY `FK9q28ewrhntqeipl1t04kh1be7` (`role_id`),
  CONSTRAINT `FK9q28ewrhntqeipl1t04kh1be7` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`),
  CONSTRAINT `FKomxrs8a388bknvhjokh440waq` FOREIGN KEY (`permission_id`) REFERENCES `sys_permission` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1'.'1');
INSERT INTO `sys_role_permission` VALUES ('2'.'2');
INSERT INTO `sys_role_permission` VALUES ('3'.'2');
INSERT INTO `sys_role_permission` VALUES ('2'.'3');

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `uid` int(11) NOT NULL,
  `username` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `salt` varchar(255) DEFAULT NULL,
  `state` int(1) DEFAULT NULL.PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1'.'admin'.'Administrator'.'123456'.'8d78869f470951332959580424d4bf4f'.'0');
INSERT INTO `sys_user` VALUES ('2'.'jiangwang'.'vip'.'123456'.'8d78869f470951332959580424d4bf4f'.'0');
INSERT INTO `sys_user` VALUES ('3'.'test'.'test'.'123456'.'8d78869f470951332959580424d4bf4f'.'0');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `role_id` int(11) DEFAULT NULL,
  `uid` int(11) DEFAULT NULL,
  KEY `FKhh52n8vd4ny9ff4x9fb8v65qx` (`role_id`),
  KEY `FKgkmyslkrfeyn9ukmolvek8b8f` (`uid`),
  CONSTRAINT `FKgkmyslkrfeyn9ukmolvek8b8f` FOREIGN KEY (`uid`) REFERENCES `sys_user` (`uid`),
  CONSTRAINT `FKhh52n8vd4ny9ff4x9fb8v65qx` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1'.'1');
INSERT INTO `sys_user_role` VALUES ('1'.'2');
INSERT INTO `sys_user_role` VALUES ('2'.'2');
INSERT INTO `sys_user_role` VALUES ('1'.'3');
INSERT INTO `sys_user_role` VALUES ('3'.'1');
Copy the code

After the database design, the following is to write the code, the business logic is very simple, after the user login successfully, according to the user’s own role and display the permissions. Login must be authenticated

Create a project

The directory structure is as follows:

Once the project is created, dependencies need to be added. I use the MyBatis framework as the persistence layer to reverse-engineer the code.

Add the dependent

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId>  </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.31.</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <! -- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
	<dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0. 0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4. 0</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>

        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.32.</version>
            <configuration>
                <overwrite>true</overwrite>
                <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
            </configuration>
        </plugin>
    </plugins>
</build>
Copy the code

After the basic code generation, the business code is written.

application.properties

server.port=7777
spring.datasource.url=jdbc:mysql:/ / 127.0.0.1:3306 / shiro_demo? serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.jw.model
mybatis.mapper-locations=classpath:mapping/*.xml
logging.level.tk.mybatis=TRACE
Copy the code

createUserService.javafile

@Service
public class UserService
{
    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;

    @Autowired
    private SysRoleMapper sysRoleMapper;

    @Autowired
    private SysRolePermissionMapper sysRolePermissionMapper;

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    public List<SysUser> getList(int id)
    {
        SysUserExample example = new SysUserExample();
        example.createCriteria().andUidEqualTo(id);
        return sysUserMapper.selectByExample(example);
    }

    /** * Query user ** based on user name@paramUsername username *@returnUser * /
    public SysUser findByUsername(String username)
    {
        SysUser user = new SysUser();
        SysUserExample example = new SysUserExample();
        example.createCriteria().andUsernameEqualTo(username);
        List<SysUser> userList = sysUserMapper.selectByExample(example);
        if (userList.isEmpty())
        {
            return null;
        }
        for (SysUser tbUser : userList)
        {
            user = tbUser;
        }
        return user;
    }


    /** * Query the user role **@paramId User ID *@returnUser role */
    public List<SysRole> findRolesById(int id)
    {
        SysUser userInfo = sysUserMapper.selectByPrimaryKey(id);
        if (userInfo == null)
        {
            throw new RuntimeException("This user does not exist");
        }
        List<SysRole> roles = new ArrayList<>();
        SysUserRoleExample userRoleExample = new SysUserRoleExample();
        userRoleExample.createCriteria().andUidEqualTo(userInfo.getUid());
        List<SysUserRole> sysUserRoleList = sysUserRoleMapper.selectByExample(userRoleExample);
        List<Integer> rids = new ArrayList<>();
        if(! CollectionUtils.isEmpty(sysUserRoleList)) {for (SysUserRole sysUserRole : sysUserRoleList)
            {
                rids.add(sysUserRole.getRoleId());
            }
            if(! CollectionUtils.isEmpty(rids)) {for (Integer rid : rids)
                {
                    SysRole sysRole = sysRoleMapper.selectByPrimaryKey(rid);
                    if(sysRole ! =null) { roles.add(sysRole); }}}}return roles;
    }


    /** * Query user permissions **@paramRoles User role *@returnUser permissions */
    public List<SysPermission> findPermissionByRoles(List<SysRole> roles)
    {
        List<SysPermission> permissions = new ArrayList<>();
        if(! CollectionUtils.isEmpty(roles)) { Set<Integer> permissionIds =new HashSet<>();// Save the menu id
            List<SysRolePermission> sysRolePermissions;
            for (SysRole role : roles)
            {
                SysRolePermissionExample sysRolePermissionExample = new SysRolePermissionExample();
                sysRolePermissionExample.createCriteria().andRoleIdEqualTo(role.getId());
                sysRolePermissions = sysRolePermissionMapper.selectByExample(sysRolePermissionExample);
                if(! CollectionUtils.isEmpty(sysRolePermissions)) {for(SysRolePermission sysRolePermission : sysRolePermissions) { permissionIds.add(sysRolePermission.getPermissionId()); }}}if(! CollectionUtils.isEmpty(permissionIds)) {for (Integer permissionId : permissionIds)
                {
                    SysPermission permission = sysPermissionMapper.selectByPrimaryKey(permissionId);
                    if(permission ! =null) { permissions.add(permission); }}}}returnpermissions; }}Copy the code

createUserController.javafile

@Controller
@RequestMapping("/userInfo")
public class UserController
{

    @GetMapping("/userList")
    public String getUserList(a)
    {
        return "userList";
    }

    @GetMapping("/userAdd")
    public String addUser(a)
    {
        return "addUser";
    }

    @GetMapping("/userDel")
    public String deleteUser(a)
    {
        return "deleteUser"; }}Copy the code

createLoginController.javafile

@Controller
public class LoginController
{
    
    @GetMapping(value = "/toLogin")
    public String toLogin(a)
    {
        return "login";
    }

    @PostMapping("/login")
    public String login(
            @RequestParam("username") String username,
            @RequestParam("password") String password,
            Model model)
    {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try
        {
            subject.login(token);
            return "index";
        }
        catch (UnknownAccountException uae)
        {
            model.addAttribute("msg"."User does not exist");
            return "login";
        }
        catch (IncorrectCredentialsException ice)
        {
            model.addAttribute("msg"."Incorrect password");
            return "login"; }}@GetMapping("/logOut")
    public String logOut(a)
    {
        return "login";
    }

    @GetMapping("/noAuthorization")
    public String noAuthorization(a)
    {
        return "This page cannot be accessed without authorization"; }}Copy the code

createCurrentUser.javafile

@Data
public class CurrentUser
{
    // Current login user
    private SysUser userInfo;

    // The role owned by the current user
    private List<SysRole> roles;

    // Permissions of the current user
    private List<SysPermission> permissions;

}
Copy the code

createMyRealm.javafile

Inherit the Shirot framework’s AuthorizingRealm class and implement the default two methods:

public class MyRealm extends AuthorizingRealm
{

    @Autowired
    private UserService userService;

    /** * authorized **@param principalCollection
     * @return* /
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
    {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // Get the current user
        CurrentUser currentUser = (CurrentUser) SecurityUtils.getSubject().getPrincipal();
        List<SysRole> roles = currentUser.getRoles();
        List<SysPermission> permissions = currentUser.getPermissions();
        if(! CollectionUtils.isEmpty(roles)) {for (SysRole role : roles)
            {
                // Authorize the roleauthorizationInfo.addRole(role.getRole()); }}if(! CollectionUtils.isEmpty(permissions)) {for (SysPermission permission : permissions)
            {
                // Grant permissionsauthorizationInfo.addStringPermission(permission.getPermission()); }}return authorizationInfo;
    }

    /** * Authentication **@param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
    {
        // The current user name
        String username = (String) token.getPrincipal();
        SysUser user = userService.findByUsername(username);
        if (user == null)
        {
            return null;
        }
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        // Put the current user's information into session
        session.setAttribute("user", user);
        // Get the role of the current user
        List<SysRole> roles = userService.findRolesById(user.getUid());
        // Get the permissions of the current user
        List<SysPermission> permissions = userService.findPermissionByRoles(roles);
        CurrentUser currentUser = new CurrentUser();
        currentUser.setUserInfo(user);
        currentUser.setRoles(roles);
        currentUser.setPermissions(permissions);
        return newSimpleAuthenticationInfo(currentUser, user.getPassword(), getName()); }}Copy the code

createShiroConfig.javafile

@Configuration
public class ShiroConfig
{
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
    {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        HashMap<String, String> filterMap = new LinkedHashMap<>();

        / / authorization
        filterMap.put("/userInfo/userAdd"."perms[userInfo:add]");
        filterMap.put("/userInfo/userDel"."perms[userInfo:del]");
        filterMap.put("/userInfo/userList"."perms[userInfo:view]");

        // The URL to intercept
        filterMap.put("/userInfo/*"."authc");
        // Pages that do not need to be blocked
        filterMap.put("/static/**"."anon");
        // The blocked page redirects to the login page
        bean.setLoginUrl("/toLogin");
        // The link to jump to after successful login
        bean.setSuccessUrl("/index");

        bean.setUnauthorizedUrl("/noAuthorization");
        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(a)
    {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(myRealm());
        return defaultWebSecurityManager;
    }

    @Bean
    public MyRealm myRealm(a)
    {
        return new MyRealm();
    }

    @Bean
    public ShiroDialect getShiroDialect(a)
    {
        return newShiroDialect(); }}Copy the code

Create the Templates folder in the Resources directory and create the following HTML files in that folder.

createindex.htmlfile

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Home page</title>
</head>
<body>
<h1>Home page</h1>

<div shiro:hasPermission="userInfo:view">
    <a th:href="@{/userInfo/userList}">Query the user</a>
</div>

<div shiro:hasPermission="userInfo:add">
    <a th:href="@{/userInfo/userAdd}">Add user</a>
</div>

<div shiro:hasPermission="userInfo:del">
    <a th:href="@{/userInfo/userDel}">Delete user</a>
</div>

</body>
</html>
Copy the code

createlogin.htmlfile

<! DOCTYPEhtml>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>The login</h1>
<hr>
<p th:text="${msg}"></p>
<form action="/login" method="post">
    <p>User name:<input type="text" name="username"/></p>
    <p>The secret code:<input type="text" name="password"/></p>
    <p><input type="submit"/></p>
</form>

</body>
</html>
Copy the code

createuserList.htmlfile

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Query the user</title>
</head>
<body>

<p>User query</p>
</body>
</html>
Copy the code

createaddUser.htmlfile

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add user</title>
</head>
<body>
<p>Add user</p>
</body>
</html>
Copy the code

createdeleteUser.htmlfile

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Delete user</title>
</head>
<body>
<p>Delete user</p>
</body>
</html>
Copy the code

Start the project

  • accesshttp://localhost:7777/toLogin, login page, enter user name and password, click Submit.

  • As you can see, the admin user has the permission to view and add, but does not have the permission to delete

  • Log in as another user and see what permissions you have.

  • It can be seen that the Jiangwang user has the permission to view, add and delete.

Shiro encryption

The passwords we save in the database are in plain text. Once the database data is leaked, it will cause unestimable losses. Therefore, we usually use asymmetric encryption, which is simply understood as irreversible encryption. In order to be more secure, we use the method of adding salt + multiple encryption.

    /** * Password encryption *@paramThe source code *@paramSalt salt *@return* /
    public static String md5Encryption(String source, String salt)
    {
        String algorithmName = "MD5";// Encryption algorithm
        int hashIterations = 1024;// Encryption times
        SimpleHash simpleHash = new SimpleHash(algorithmName, source, salt, hashIterations);
        return simpleHash + "";
    }
Copy the code

summary

The application of permissions in our project is very extensive, involving permissions can include: Login permissions, menu access, data access permissions (button), the demo above you can see different user logged in, have different access permissions (data), our projects, involving permissions can have these a few tables, the user table, table, permissions list, users and roles is a many-to-many relationship, roles and permissions is a many-to-many relationship.

Complete code managed code cloud: gitee.com/jiangwang00…