头图

What can Shiro do for us?

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro's easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications. - "Apache Shiro Official Website"

Apache Shiro is an easy-to-use and powerful security framework in the Java field, mainly used for login authentication, authorization, encryption, and session management. Shiro has a simple application API that you can quickly use to protect your application, whether it is a small mobile application or a large web enterprise application.

From the above passage we can extract the following information:

  • Shiro is easy to use and powerful
  • Mainly used in login authentication, authorization, encryption, session management.

The first point needs to be slowly realized in the use of China. The second point is our main concern. Here we will talk about it bit by bit.

Login authentication authentication and session management

Here we recall the HTTP protocol and Servlet. The early HTTP protocol was stateless. We can understand this statelessness. If you visit a website a minute ago and visit a website now, the server does not know who you are, but for Web services For end developers, access control cannot be implemented, and some information can only be viewed by logged-in users, each of which can be viewed individually. This really limits the development of the Web. In order to make the HTTP protocol stateful, the RFC-6235 proposal was approved. This proposal introduced a cookie, which is a small piece of data that the server sends to the user's browser and saves it locally. It will be carried and sent to the server when the browser initiates a request to the same server next time, and the server can realize the "identification" of the user.

In a native servlet scenario the following is true:

登录流程图

Code sample:

 /**
 * 拦截所有的请求
 */
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {

    /**
     * 不重写init方法,过滤器无法起作用
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("----login check filter init-----");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpSession session = servletRequest.getSession();
        // 这里采取了暂时写死,目前只放行登录请求的URL
        String requestUri = servletRequest.getRequestURI();
        if ("/login".equals(requestUri)){
            // 放行,此请求进入下一个过滤器
            chain.doFilter(request,response);
        }else {
            Object attribute = session.getAttribute("currentUser");
            if (Objects.nonNull(attribute)){
                chain.doFilter(request,response);
            }else {
                request.getRequestDispatcher("/login.jsp").forward(request,response);
            }
        }
    }
}
@WebFilter(urlPatterns = "/login")
public class LoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("------login filter init-----");
    }

    /**
     * 由登录过滤器来执行登录验证操作
     * 要求用户名和密码不为空的情况下才进行下一步操纵
     * 此处省略判空操作
     * 只做模拟登录
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest servletRequest = (HttpServletRequest) request;
        String userName = (String) servletRequest.getAttribute("userName");
        String password = (String)servletRequest.getAttribute("password");
        // 这里假装去数据库去查账号和密码
        HttpSession session = servletRequest.getSession();
        // 生成session,
        session.setAttribute("currentUser",userName + password);
        // 放到下一个过滤器,如果这是最后一个过滤器,那么这个请求会被放行到对应的Servlet中
        chain.doFilter(request,response);
    }
}
@WebServlet(value = "/hello")
public class HttpServletDemo extends HttpServlet {
   
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("hello world");
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

As mentioned earlier, in order to realize the "state" of the HTTP protocol, the browser introduced a cookie to store the data sent by the server to the client. In order to make the server distinguish different users, the server program introduced the concept of Session. When requesting the server, the server will tell the client in the HTTP protocol that a SessionID needs to be recorded in the Cooke, and the SessionID will be sent to the server for each subsequent request, so that the server can distinguish different clients.

JSessionId

With such a correspondence, we can save the current user's information in the Session. In fact, we only log in here, and have not logged out yet. Logout should clear the corresponding Session. This set of logic Shiro has done for us, which is naturally excellent, but just like this, it is not enough for us to use Shiro, so let's go through the official documentation.

an episode

Because I haven't written Servlet for a long time, I downloaded Tomcat this time and downloaded a 10.0 version, but found that the 10.0 version is not compatible with JDK 8. After a long time, the request could not reach the Servlet, or it was reported that the Servlet initialization failed, the highest I had to go back to version 8.5, but I still found that the request could not reach the servlet I wrote. I configured the mapping based on annotations, but there is an attribute in web.xml that I forgot, that is metadata-complete, this attribute is true, Annotation-based servlets will not be scanned. If this annotation is false, this servlet will be enabled.

meta-complete = false

Features of Shiro

ShiroFeatures

Authentication and Session Management: Shiro did it for us, which is great.

Cryptography: Encryption, in fact, the Java standard library also provides an implementation, it is better that Shiro can provide a simpler and easier-to-use API.

Authorization: Authorization, this is worth our attention. When I write this, I think of the first Web system I built. At that time, I only considered ordinary users and did not consider the issue of resource control. At that time, it was almost the scheduled time. Ashamed, I had to do a very rough permission control, adding a permission identification field for the user, 1 for what role, 2 for what role can see which pages, that is to say, the resources are fixed and quite rigid. I learned later that there is a relatively mature and natural RBAC (Role-Based Access Controle Role-Based Access Control) model in this regard, that is, role-user-authority (resource), that is to say, a user has several roles, each of which Roles have several permissions and resources, so we can decouple permissions and users.

After we have roughly discussed the basic concepts, let's take a closer look at these features:

  • Login

    • Subject Based – Almost everything you do in Shiro is based on the currently executing user, called a Subject.
      And you can easily retrieve the Subject anywhere in your code. This makes it easier for you to understand and work with Shiro in your applications.

      Based on the subject, anything you do in Shiro is based on the currently active user, which is called the subject in Shiro, and you can get the current subject anywhere in the code.

      This will make it easier for you to use and understand shiro.

    • Single Method call – The authentication process is a single method call.
      Needing only one method call keeps the API simple and your application code clean, saving you time and effort.

      Simple method calls, very simple, save time and effort.

    • Rich Exception Hierarchy – Shiro offers a rich exception hierarchy to offered detailed explanations for why a login failed.
      The hierarchy can help you more easily diagnose code bugs or customer services issues related to authentication. In addition, the richness can help you create more complex authentication functionality if needed.

      Rich exception system, Shiro provides a complete exception system to explain why the login fails. This exception system can help diagnose bugs and issues related to custom services. In addition, it can also help create more feature-rich applications.

    • 'Remember Me' built in – Standard in the Shiro API is the ability to remember your users if they return to your application.
      You can offer a better user experience to them with minimal development effort.

      Remember me, the standard Shiro API provides the ability to log passwords, and with a small amount of configuration, provides a better user experience.

    • Pluggable data sources – Shiro uses pluggable data access objects (DAOs), called Realms, to connect to security data sources like LDAP and Active Directory.
      To help you avoid building and maintaining integrations yourself, Shiro provides out-of-the-box realms for popular data sources like LDAP, Active Directory, and JDBC. If needed, you can also create your own realms to support specific functionality not included in the basic realms.

      Pluggable data source, Shiro provides a pluggable data permission object, we call it Realms in Shiro, we use this to securely connect data sources like LDAP and Active Directory.

    In order to avoid developers to do duplication of work, Shiro provides out-of-the-box connection to the specified data source Realm is, like LDAP, Active Directory, JDBC. You can also create custom Realms if you need.

    • Login with one or more realms – Using Shiro, you can easily authenticate a user against one or more realms and return one unified view of their identity.
      In addition, you can customize the authentication process with Shiro's notion of an authentication strategy. The strategies can be setup in configuration files so changes don't require source code modifications – reducing complexity and maintenance effort.

      Support one and multiple Realm logins, with Shiro you can easily complete the user login with multiple Realm, and the way is unified. In addition, you can also use Shiro's authentication strategy to customize the login process, the authentication strategy support is written in the configuration file, so when the authentication strategy changes, no code changes are required.

It seems that the above feature overview, you will find that the original login needs to be considered so much. Originally, our perspective may only be on the data source of the database. In fact, other data sources can be introduced to the WEB system, but you don't have to worry about it, so many Things that need to be considered, you originally wrote the login yourself, and you may also consider the omissions, but Shiro has written it for you. Why don't we learn it. Here we still need to focus on Shiro's Realms concept. Let's recall JDBC, which allows us Java programmers to write a set of APIs to cross databases. As for multiple data sources, can we also abstract an interface to do What about cross data sources when it comes to logging in? In fact, Realms is this idea, and it also abstracts an interface to connect with different data sources to realize login authentication:

Realm继承图

It is essentially a specific security DAO (Data Access Object), which encapsulates the details of the connection with the data source and obtains the relevant data required by Shiro. When configuring Shiro, we must specify at least Realm to implement authentication and authorization.

We only focus on one feature to experience the power of Shiro, and we only briefly introduce the other features in the blink of an eye:

  • Cryptography: Shiro pointed out that the Java Cryptography Extension (JCE) can be complicated and difficult to use unless you're a cryptography expert I didn't say it), the cryptographic API designed by Shiro is simpler and easier to use.

    PS: In fact, it also pointed out some problems with the extension of Java cryptography, which is regarded as a spit on Java cryptography-related libraries. If you are interested, you can go and have a look. We will not introduce too much here.

  • SessionManagement

    Can be used for SSO, it is quite convenient to get user login and logout. SSO is a single sign-on, which is convenient to integrate with various application systems.
  • Authorization

    The following are some classic problems of authorization:

    Whether this user can edit this account

    Does this user have permission to view this page?

    Does this user have permission to use this button?

    Shiro answers the above questions and is very flexible, simple, and easy to use.

    Shiro has done so much for us, and it's simple, we can save a lot of work, that's the reason to learn Shiro.

Overview of Shiro Core Concepts

There are three main concepts in the architecture in Shiro:

  • subject (the current subject, which can be understood as the current logged-in user,)

    In Shiro, you can use the following code to get the currently logged in user:

 Subject currentUser = SecurityUtils.getSubject();
  • SecurityManager
Provide security protection for all users, there are many security components embedded, so how to set it up? It also depends on different environments. In Web programs, Shiro's servlet filter is usually specified in Web.xml, which completes an instance of SecurityManager. We also have other options for other types of applications.
  • Realms
Realm is actually a bridge or connector between Shiro and your application's secure data.

Realms

The relationship between the three:

三个验证状态

use it

First introduce maven dependencies:

 <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.9.0</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.21</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.7.21</version>
      <scope>test</scope>
    </dependency>

Starting with Hello World of course

 public static void testAuthentication(){
        // 设置SecurityManager 
        // SecurityManager 负责将用户提交过来的username、password和realm中的进行对比
        // 判断是否可以登录
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
        // 这是一个简单的Realm,直接在代码里面存储账号和密码
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
        simpleAccountRealm.addAccount("hello world","hello world");
        // 将realm 纳入到 DefaultManager的管辖之下
        defaultSecurityManager.setRealm(simpleAccountRealm);
        // 通过此方法设置SecurityManager
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        
        // 用户登录和密码凭证
        UsernamePasswordToken token = new UsernamePasswordToken("hello world", "hello world");
        
        // 获取subject
        Subject subject = SecurityUtils.getSubject();
        
        // 由subject将token提交给SecurityManager
        subject.login(token);
        // 登录成功会返回true
        System.out.println("login status:"+subject.isAuthenticated());
        // 退出
        subject.logout();
        // 退出之后是false
        System.out.println("login status:"+subject.isAuthenticated());
    }

Encryption example

Cryptography is the process of hiding or obfuscating data so prying eyes can't understand it. Shiro's goal in cryptography is to simplify and make usable the JDK's cryptography support.

Cryptography is the process of hiding or obfuscating data, preventing it from being stolen. Shiro's goal in cryptography is to simplify the use of the JDK's standard cryptography library

Next let's see how easy it is to use Shiro's cryptography-related APIs.

  • Implementation of MD5 JDK Standard Library
 private static void testMD5JDK() {
        try {
            String code = "hello world";
            // MD5 是 MessageDigest的第五个版本
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] targetBytes = md.digest(code.getBytes());
            //输出的是MD5的十六进制形式
            System.out.println(targetBytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
  • Shiro's implementation:
 private static void testMD5Shiro() {
    String hex = new Md5Hash("hello world").toHex();
    System.out.println(hex.getBytes());
}

In this way, Shiro's implementation is indeed simpler and more intuitive.

Authorization Example

Authorization is divided into the following two types in Shiro:

  • Permission Defined

Permissions are the most atomic level of a security policy and they are statements of functionality. Permissions represent what can be done in your application. A well formed permission describes a resource types and what actions are possible when you interact with those resources. Can you open a door ? Can you read a file ? Can you delete a customer record ? Can you push a button ?

Common actions for data-related resources are create, read, update, and delete, commonly referred to as CRUD.

It is important to understand that permissions do not have knowledge of who can perform the actions– they are just statements of what actions can be performed.

Permits are atomic levels of security policy, expressed as functional declarations. Licensing means what you can do in the system. Well-formed permissions describe the types of resources and the operations that can be performed on those resources. For example: Can you open this door? Can you read a file? Can you delete a customer record? Can you click the button?

Generally speaking, operations on resources include adding, reading, updating, and deleting, and these operations are usually referred to as CRUD.

It's important to understand that permissions don't know who can operate those resources, they just describe what those resources can do.

​ Granularity of permissions:

  1. Resource Level - This is the broadest and easiest to build. A user can edit customer records or open doors. The resource is specified but not a specific instance of that resource.
Resource level: This is the most extensive and the easiest to build. Users can edit customer records or open a door. Resources are assigned, but not assigned to specific man-made roles.
  1. Instance Level - The permission specifies the instance of a resource. A user can edit the customer record for IBM or open the kitchen door.
Instance level: A permission specifies a specific person or role, a user can edit IBM's user records or open the kitchen door.
  1. Attribute Level - The permission now specifies an attribute of an instance or resource. A user can edit the address on the IBM customer record.
Attribute level: Someone is allowed to edit an attribute of the resource, and a user can edit the address of the IVM user record.
  • Role Defined

In the context of Authorization, Roles are effectively a collection of permissions used to simplify the management of permissions and users. So users can be assigned roles instead of being assigned permissions directly, which can get complicated with larger user bases and more complex applications. So , for example, a bank application might have an administrator role or a bank teller role.

In the upper and lower doors of authorization, roles are actually a collection of permissions, which simplifies the management of permissions and users, so users can be assigned roles, rather than directly assigned permissions, because directly assigning permissions is important for larger user groups and more complex The application will be more complicated.

Shiro supports the following two roles:

  • Implicit Roles Implicit Roles
Most people's roles are implicit roles in our eyes, which implies a set of permissions, generally speaking, if you have the role of administrator, you can view patient data. If you have the role of "Bank Teller", then you can create an account.
  • Explicit Roles Explicit Roles
Explicit roles are permissions that are clearly assigned by the system. If you can view patient data, it is because you have been assigned the "View Patient Data" permission in the "Administrator Role".
A little summary

The above is my translation of Shiro's discussion of roles and permissions. In terms of permissions, it can be understood as CRUD for a certain resource. The granularity level has the entire resource, such as the manipulation permission of a row of records. This row of records can only be manipulated by a certain person. Can manipulate part of this line of records. A role is a collection of permissions. In my opinion, it is to realize the decoupling of permissions and users. For example, if I want to authorize a batch of users, I can choose a role and perform batch authorization. Then the question comes again, how do I do permission control, or determine whether a certain role has this role in Shiro?

  • We can judge whether the current user has a certain role or has a certain permission from the methods of the Subject class

     subject.hasRole("admin") // 当前用户是否有admin这个角色
    subject.isPermitted("user:create") // 判断当前用户是否允许添加用户
  • JDK's annotations, annotating methods
 // 判断当前角色是否有admin 这个角色
@RequiresRoles("admin")
private static void requireSpecialRole() {

}
 // 判断当前用户是否允许添加用户
@RequiresPermissions("user:create")
private static void requireSpecialRole() {
 }
  • JSP tags (the era of separation of front and back ends, this will not be introduced)

Shiro defines a set of permission description syntax to describe permissions, the format is: resource:(create || update || delete ||query).

Example:

 user:create,update // 表示具备创建和更新用户的权限
user:create,update:test110 // ID为test110有 创建和更新用户的权限

Custom Realm

We have been saying that Realm is the bridge between Shiro and user data. Let's take a general look at the login process and focus on the bridge:

  1. subject.login(token). Currently Subject has only one implementation class, which is DelegatingSubject.

DelegatingSubject

We used DefaultSecurityManager above, so we need to enter the method of this class to see how to perform login. DefaultSecurityManager.login calls authenticate(token) to obtain user information. The authenticate() method comes from DefaultSecurityManager's parent class AuthenticatingSecurityManager, which has An Authenticator member variable, which is used to call authenticate to obtain user information

AuthenticatingSecurityManager

This is somewhat like the template method pattern, where the parent class defines the skeleton or generic method, and the child class calls it. Authenticator is an interface with many implementation classes, but the call to authenticate has only two implementations:

AbstractAuthenticator

doAuthenticate

获取初始化进来Realms

最终调到了对应的Realms

The interface of Realm is too top-level. If we want to make our own Realm, we still need to find an abstract class. The SimpleAccountRealm above is inherited from AuthorizingRealm, and rewrites doGetAuthenticationInfo for login verification and doGetAuthorizationInfo for authorization verification. We wrote it ourselves as follows:

 public class MyRealm extends AuthorizingRealm {

    /**
     * 设置Realm的名称
     */
    public MyRealm() {
        super.setName("myRealm");
    }

    /**
     * 我们这个自定义reamls就能实现权限控制了
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取登录凭证
        String userName = (String) principals.getPrimaryPrincipal();
        // 假装这roles 是从数据库中查的
        Set<String> roles  = new HashSet<>();
        // 假装是从数据库查的
        Set<String> permissions  = new HashSet<>();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }

    /**
     * 随便写个空实现
     * @param userName
     * @return
     */
    private String getPassWordByUserName(String userName) {
        return "";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取账号
        String userName = (String) token.getPrincipal();
        String password = null;
        if (userName != null && userName != ""){
            password = getPassWordByUserName(userName);
        }else {
            return null;
        }
        if (Objects.isNull(password)){
            return null;
        }
        // 假装去查了数据库
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,"myRealm");
        return simpleAuthenticationInfo;
    }
}

write at the end

I remember that when I first started learning Shiro, I went to station B to search for corresponding videos, but most of the videos started with the basic components of Shiro. I actually just wanted to know what Shiro could do for me. I started talking about a security framework, and then talked about the architecture. After watching the video for a long time, I only got a bunch of nouns, and I didn’t see what I wanted to see, which really consumed my patience. Therefore, this article is problem-oriented, that is, what Shiro did for us in the end, and also adjusted the introduction style of some articles, omitting some relatively large words. This article is dedicated to the original search for Shiro on the Internet. I haven't found a suitable tutorial for myself.

References


北冥有只鱼
147 声望35 粉丝