0) preface

The last article [Shiro – First Experience] explained the simple usage of Shiro, which realizes whether the URL needs login access, and automatically jumps to the login page when the URL is not logged in.

This article focuses on how to implement login processing in Shiro. First, a brief description of Shiro’s login process.

Shiro handles login in the AuthC filter. Authc determines that login requests will be processed separately, so login requests must be set to AuthC.

There are two login requests:

  • Access login: The login page is displayed
  • Submit login: a request made by clicking the login button on the login page

Shiro recognizes that the two login requests must be at the same address, and GET means access to the login page and POST means submit to the login

// Login request (including accessing the login page and submitting the login)if(isLoginRequest(request, response)) {// Submit loginif(isLoginSubmission(Request, Response)) {// Submit the login and execute Shiro's login logicreturn executeLogin(request, response);
    } else{// Access the login request, proceed to enter the controllerreturn true; }}Copy the code

In the previous article, our request to access the login page was /login.jsp, which accesses the login JSP directly. If we use POST to access the JSP, it is obviously inappropriate to use JSP.

Therefore, we change the login request to /login to handle GET and POST in the controller. Shiro needs to configure when changing the login request address

Shiro core configuration shiroFilter(ShiroFilterFactoryBean) {// loginUrl (including request login page and submit login) // custom loginUrl must be set separately loginUrl ="/login"/ /... }Copy the code

Accordingly, two methods are added to the controller to handle the login request and submit the login

// Process the request login page @getMapping ("/login")
public String toLogin() {
    return "/login"; } // Handle submission logins @postmapping ("/login")
public String login() {
    System.out.println("Process submit login");
    return "/success";
}
Copy the code

Login page: login.jsp

<form action="/login" method="POST">
	<input type="text" name="username" placeholder="Username" value="" />
	<input type="password" name="password" placeholder="Password" value="" />
	<input type="submit" value="Log in now" />
</form>
Copy the code

After completing the above operations, start the project. When accessing /page/ A, Shiro will redirect to /login because you have not logged in. Enter the user name and password on the login page and click the login now button to submit to /login by POST.

  • The username and password name must be username and password (Shiro will take the values of these two parameter names from the Request as the username and password)
  • The request must be POST and the request address must match the loginUrl in the Shiro configuration file.

So, how does Shiro know if the username and password you entered are correct?

The answer must be no, therefore, we need to verify the user name and password and tell Shiro the result. So how do you implement custom validation?

1) Customize the Realm

Shiro’s definition of Realm: A component that can access system security-related information such as users, roles, permissions, and so on. Write user, role, permission, and other security-related data in a Realm.

The user information is typically stored in the database, and we can query the user in the database using the user name passed by the login page in Realm and return the result to Shiro.

However, Shiro does not know whether the user name and password are correct, so we provide a Realm component that allows us to query the user’s information in the Realm and return the result. Shiro determines whether the login is successful based on the result returned by the Realm.

For example

When you invite a girl to dinner on a blind date, you never know what she would like to eat. But you have a restaurant for each cuisine. You are smart enough to prepare a small box. During the blind date, you ask the girl to write down what she wants to eat and put it in the box. Then you go to the restaurant prepared in advance to have a meal according to the contents in the box. You don’t care if a girl wrote it in pencil or pen, you only care what she wants.

In the above example, you are Shiro, preparing various restaurants is the realization of various logins, the small box is the realization of a Realm, the note written by the girl is the realization of a Realm, the content of the note is the user information queried. Whether to write in pencil or pen is equivalent to the user information retrieval method (database, file or other).

Shiro only cares about the results returned, not the implementation of a Realm query for user information. Let’s implement a Realm

Public class UserRealm extends AuthenticatingRealm {Override protected AuthenticationInfodoGetAuthenticationInfo(AuthenticationToken Token) throws AuthenticationException {// Login user name // String username = ((UsernamePasswordToken) token).getUsername(); User dbUser = getUser(username); User dbUser = getUser(username); // The user does not exist. When null is returned, Shiro assumes that the user information does not existif (dbUser == null) {
            returnnull; // Parameter 1: Shiro will save this parameter as the current login user information, available at any time. // Parameter 2: the current user password,Shiro uses this parameter and the password submitted to the login pass to determine // parameter 3: the name of the Realmreturn new SimpleAuthenticationInfo(dbUser, dbUser.getPassword(), ""); }}Copy the code
  • Inherit AuthenticatingRealm and implement the doGetAuthenticationInfo method to get user information
  • Returns the AuthenticationInfo object required by Shiro when the user information is queried based on the user name (built-in multiple return objects, described later)
  • If no user is found, Shiro will return null. If the result is null, Shiro will treat the user as if the user does not exist. This login fails
  • If the password is correct, the login succeeds. If the password is correct, the login succeeds. Error this login fails
  • Shiro doesn’t care how user information is retrieved, whether it’s a database query, a file query, or a third-party interface, as long as it’s returned in a format.
  • Shiro will save the first parameter object of the returned result. After successful login, related information of the login user can be obtained through Shiro’s method (for example, obtaining the login user ID).

This example is not connected to the database, simulation code:

Private User getUser(String username) {// Use"atd681"As a login password to access informationif (!"atd681".equals(username)) {
        return null;
    }
 
    User dbUser = new User();
    dbUser.setUserId(1L);
    dbUser.setUsername(username);
    dbUser.setPassword("123");
 
    return dbUser;
}
Copy the code
  • Valid user name: ATd681, password :123, and user ID:1
  • Failed to log in to other users

2) configuration Realm

After you customize a Realm, you need to tell Shiro which Realm is used to query user information

/ / security manager securityManager (DefaultWebSecurityManager) {realm = ref ("userRealm"} // Define Realm userRealm(userRealm)Copy the code
  • Define a Realm in the Shiro configuration file
  • Configure the Realm into the securityManager securityManager

Start the project, access /page/a, Shiro redirects to the login page if not logged in. Enter ATD681/123 to log in successfully and jump to /page/ A

3) Configure the default success page

When the login is successful, Shiro redirects to the success page

  • When visiting other pages (/page/a) To login, the successful login will redirect to the target page (/page/a)
  • Access the login page (no target page) and go to the default success page after successful login

Shiro’s default success page is /. You can customize the default success page

Shiro Core configuration shiroFilter(ShiroFilterFactoryBean) {// Default page address successUrl ="/index"// Other configuration... }Copy the code

4) Process the login failure

The POST login method is not executed in the controller after a successful login. If you enter an account other than ATD681 or enter an incorrect password, the login fails, but the controller POST login method is executed. Why??

Shiro’s login logic:

  • When you visit the login page, Shiro does not process and enters the controller
  • After successful login, direct redirection to the success page (without entering the controller)
  • If the login fails, the controller handles the login failure

When a login fails, Shiro uses an exception to indicate the cause of the failure and saves the failure reason in the Request. The key is shiroLoginFailure. Shiro’s login logic will throw the following exception:

  • User does not exist:org.apache.shiro.authc.UnknownAccountException
  • Incorrect password:org.apache.shiro.authc.IncorrectCredentialsException

At the same time, the following exceptions are built in, so that users can throw them when verifying themselves:

  • Invalid user:org.apache.shiro.authc.DisabledAccountException
  • Locked users:org.apache.shiro.authc.LockedAccountException
  • Too many failures:org.apache.shiro.authc.ExcessiveAttemptsException
  • User logged in:org.apache.shiro.authc.ConcurrentAccessException

If the login fails, an error message is displayed on the page. In this example, if the login fails, the login page is displayed with an error message

<! -- If there is a login error, display the corresponding prompt message according to the exception --> <c:if test="${shiroLoginFailure ! = null}">
	<c:if test="${shiroLoginFailure == 'org.apache.shiro.authc.UnknownAccountException'}"> user does not exist </c:if>
	<c:if test="${shiroLoginFailure == 'org.apache.shiro.authc.IncorrectCredentialsException'}"> Incorrect password </c:if>
</c:if> <! --> <c:if test="${shiroLoginFailure == null}"</c:if>
 
<form action="/login" method="post">
	<input type="text" name="username" placeholder="Username" value="" />
	<input type="password" name="password" placeholder="Password" value="" />
	<input type="submit" value="Log in now" />
</form>
Copy the code

5) to log out

To configure the logout URL, use the logout filter. Shiro is redirected to the login page by default after logging out.

// Shiro core configuration shiroFilter(ShiroFilterFactoryBean) {// Configure URL rules // Shiro will find the corresponding filter if there is a request for access filterChainDefinitionMap = ["/page/n" : "anon", // /page/n No login required to access"/logout" : "logout"// log out to uselogoutThe filter"/ * *": "authc"// All other pages need authentication (authc is authentication filter)] // Other configurations.... }Copy the code

If you customize the redirection page after logout, you will need to manually define the logout filter in the configuration file (Shiro will load it automatically through Spring if not).

// Manually define the Logout filter // Shiro is automatically loaded via Spring if it is not definedlogout(LogoutFilter){
    redirectUrl = "/logout_success.jsp"
}
Copy the code

Also, logout_success.jsp must be configured to be accessible without login (anon). If not configured, Logout_success.jsp will be blocked by Shiro and redirected to logon (logout_success.jsp will be redirected to logout_success.jsp after successful login)

"/logout_success.jsp" : "anon"// No authentication is required to log out of the success pageCopy the code

6) Obtain login user information

In 1) Custom Realm it is mentioned that the user information obtained after a successful login is saved by Shiro. Shiro provides a way to get information about the logged-in user.

@RequestMapping("/page/a") public String toPageA(ModelMap map) {// Shiro provides static method to obtain the current User information // User information object is stored in the object User User = (User) SecurityUtils.getSubject().getPrincipal(); // Get user ID, user name map.put("userId", user.getUserId());
    map.put("userName", user.getUsername());
 
    return "/page_a";
}
Copy the code

The user object retrieved must be the same as the first parameter returned in the SimpleAuthenticationInfo object in Realm

7) Sample code

At this point, the sample configuration based on Shiro authentication is complete.

  • Example code address: github.com/atd681/alld…
  • Example project name: ATD681-Shiro-Authc