Apache Shiro is a powerful and flexible open source security framework that handles Authentication, Authorization, Common security control processes in enterprise applications such as Session management and cryptography. The primary goal of Apache Shiro is ease of use and understanding. Sometimes controlling the flow of security can be complex and a headache for developers, but it doesn’t have to be. The framework should hide as much complexity as possible and expose a concise and intuitive API to simplify developers’ lives and ensure their applications are secure. This time we’ll talk about how to use Shiro for permission control in Spring Web applications.

function

Apache Shiro is a comprehensive application security framework with many features. Here are some of Shiro’s most important features:

  • Authentication: Also known as “login,” in order to prove the owner of the user’s actions.
  • Authorization: The process of access control, that is, determining which users can access what.
  • Session management: The ability to manage user-specific sessions, even in non-Web applications, is one of Shiro’s strengths.
  • Encryption technology: The use of encryption algorithms to ensure the security of data, very easy to use.

architecture

As a whole, Shiro’s architecture has three main concepts: Subject (i.e. user), Security Manager (Security Manager), and Realms (Realm). The following diagram depicts the relationship between these components:

  • Subject: A Subject is a specific set of data for the user that is currently operating on. The principal can be a person, or it can represent a third-party service, a daemon, a scheduled task, or something similar, that is, almost anything that interacts with the application.
  • Security Manager: It is the core of Shiro’s architecture and acts as an “umbrella” that coordinates internal components to form a safety net.
  • Realms: A “bridge” between Shiro and application security data. When you need to actually interact with security-related data such as user accounts to perform authentication and authorization, Shiro pulls that data from Realms.

Data preparation

In Web applications, security is controlled by roles, resources, and permissions. A user can have multiple roles and a role can access multiple resources, that is, a role can correspond to multiple permissions. To implement the database design, we need to build at least five tables: user table, role table, resource table, role-resource table, user-role table, the structure of these five tables is as follows: user table:

id username password
1 Zhang SAN 123456
2 Li si 666666
3 Cathy 000000

Character sheet:

id rolename
1 The administrator
2 The manager
3 employees

Resource list:

id resname
1 /user/add
2 /user/delete
3 /compony/info

Role-resource table:

id roleid resid
1 1 1
2 1 2
3 2 3

User-role table:

id userid roleid
1 1 1
2 1 2
3 1 3

The corresponding POJO class is as follows:

/**
 * 用户
 */
public class User {
 private Integer id;
 private String username;
 private String password;
    //getter & setter...
}
Copy the code
/**
 * 角色
 */
public class Role {
    private String id;
    private String rolename;
}
Copy the code
/** * resources */
public class Resource {
    private String id;
    private String resname;
}
Copy the code
/** * Roles - Resources */
public class RoleRes {
    private String id;
    private String roleid;
    private String resid;
}
Copy the code
/** * User - role */
public class UserRole {
    private String id;
    private String userid;
    private String roleid;
}
Copy the code

For detailed steps on integrating Spring with Shiro, see my blog, Integrating Apache Shiro in Spring Applications. Here’s a sidebar: To import Shiro’s dependencies, go to mvnRepository.com and search for Shiro. We need the first three dependencies, namely Shro-core, Shro-Web, and Shro-Spring. Take the Maven project as an example. Add the following dependencies under the

node in pom.xml:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
Copy the code

In application-context.xml, you need to configure the shiroFilter bean like this:

<! -- Configure shiro's filter factory class, id-ShiroFilter to be the same as the filter we configured in web.xml -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <! -- Login page -->
    <property name="loginUrl" value="/login"/>
    <! -- Successful login page -->
    <property name="successUrl" value="/index"/>
    <! -- Unauthorized access to jump page -->
    <property name="unauthorizedUrl" value="/ 403"/>
    <! -- Permission Configuration -->
    <property name="filterChainDefinitions">
        <value>
            <! -- Static resources that can be accessed without authentication, and other urls can be added -->
            /static/** = anon
            <! -- All resources except those ignored above need authentication to access -->
            /** = authc
        </value>
    </property>
</bean>
Copy the code

Now we need to define a Realm. The custom Realm integrates with the AuthorizingRealm class:

public class MyRealm extends AuthorizingRealm {
 @Autowired
 private UserService userService;
 /** * Verify permission */
 @Override
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  String loginName = SecurityUtils.getSubject().getPrincipal().toString();
  if(loginName ! =null) {
   String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString();
   // Permission information object, used to store all roles and permissions of the detected user
   SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
   // Set of user roles
   ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
         info.setRoles(shiroUser.getRoles());
         info.addStringPermissions(shiroUser.getUrlSet());
   return info;
  }
  return null;
 }
 /** * Authentication callback function, called */ when logging in
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
  String username = (String) token.getPrincipal();
  User user = new User();
        sysuser.setUsername(username);
  try {
   List<SysUser> users = userService.findByNames(user);
            List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId());
   if(users.size() ! =0) {
    String pwd = users.get(0).getPassword();
    // After all the authentication passes, the user information is stored in the session
    Session session = SecurityUtils.getSubject().getSession();
    session.setAttribute("userSession", users.get(0));
    session.setAttribute("userSessionId", users.get(0).getId());
    session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,","));
                return new SimpleAuthenticationInfo(username,users.get(0).getPassword());
   } else {
                // The user was not found
    throw newUnknownAccountException(); }}catch (Exception e) {
   System.out.println(e.getMessage());
  }
  return null;
 }
 /** * Update user authorization information cache. */
 public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
  super.clearCachedAuthorizationInfo(principals);
 }
 /** * Update user information cache. */
 public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
  super.clearCachedAuthenticationInfo(principals);
 }
 /** * Clears the user authorization information cache. */
 public void clearAllCachedAuthorizationInfo(a) {
  getAuthorizationCache().clear();
 }
 /** * Clear the user information cache
 public void clearAllCachedAuthenticationInfo(a) {
  getAuthenticationCache().clear();
 }
 /** * Clear all caches */
 public void clearCache(PrincipalCollection principals) {
  super.clearCache(principals);
 }
 /** * Clear all authentication caches */
 public void clearAllCache(a) { clearAllCachedAuthenticationInfo(); clearAllCachedAuthorizationInfo(); }}Copy the code

Finally, define a controller for user login to accept user login request:

@Controller
public class UserController {
    /** * User login */
    @PostMapping("/login")
    public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){
        try {
            if(bindingResult.hasErrors()){
                return "login";
            }
            // Use the permissions tool for authentication, and after successful login, jump to the successUrl defined in the shiroFilter bean
            SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
            return "redirect:index";
        } catch (AuthenticationException e) {
            redirectAttributes.addFlashAttribute("message"."Wrong username or password");
            return "redirect:login"; }}/** * Log out */
    @GetMapping("/logout")
    public String logout(RedirectAttributes redirectAttributes ){
        SecurityUtils.getSubject().logout();
        return "redirect:login"; }}Copy the code