Java Second Kill System Practical Series ~Integrating Shiro to Realize User Logon Authentication

Summary:

This blog is the fifth in the series of "Java Second Kill System Actual Warfare Articles". In this blog, we will integrate the authority authentication-authorization framework Shiro to realize the user's login authentication function. It is mainly used to require users to restrict the user's login when they rush to buy goods or kill goods in seconds. And filter the specific URL (such as the URL corresponding to the snap-up request). (that is, when the user accesses the specified url, the user is required to log in.)

Contents:

For Shiro, I believe that your little friends should have heard of it and even used it! Simply speaking, it is a very useful user identity authentication and authorization framework, which can realize user login authentication, authorization, resource authorization, session management and other functions. In this second kill system, we will mainly use this framework to achieve user identity authentication and user login function.

It is worth mentioning that the function module of "Shiro realizes user login authentication" introduced in this blog article involves the database table of user information table.

<!--shiro Privilege control-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>${shiro.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>

(2) In the User Controller controller, the functional methods corresponding to user's requests for login, user login and user exit login are developed. The complete source code is as follows:

@Autowired
private Environment env;

//Jump to the login page
@RequestMapping(value = {"/to/login","/unauth"})
public String toLogin(){
    return "login";
}

//Login Authentication
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(@RequestParam String userName, @RequestParam String password, ModelMap modelMap){
    String errorMsg="";
    try {
        if (!SecurityUtils.getSubject().isAuthenticated()){
            String newPsd=new Md5Hash(password,env.getProperty("shiro.encrypt.password.salt")).toString();
            UsernamePasswordToken token=new UsernamePasswordToken(userName,newPsd);
            SecurityUtils.getSubject().login(token);
        }
    }catch (UnknownAccountException e){
        errorMsg=e.getMessage();
        modelMap.addAttribute("userName",userName);
    }catch (DisabledAccountException e){
        errorMsg=e.getMessage();
        modelMap.addAttribute("userName",userName);
    }catch (IncorrectCredentialsException e){
        errorMsg=e.getMessage();
        modelMap.addAttribute("userName",userName);
    }catch (Exception e){
        errorMsg="User login exception, please contact administrator!";
        e.printStackTrace();
    }
    if (StringUtils.isBlank(errorMsg)){
        return "redirect:/index";
    }else{
        modelMap.addAttribute("errorMsg",errorMsg);
        return "login";
    }
}

//Log out
@RequestMapping(value = "/logout")
public String logout(){
    SecurityUtils.getSubject().logout();
    return "login";
}

When matching user's password, we use the Md5Hash method here, that is, MD5 encryption to match (because the user's password field in the database user table stores the encrypted string encrypted by MD5).

The content of the front-end page login.jsp is relatively simple. It only needs the user to enter the most basic user name and password. As shown in the figure below, some of the core source code of the page is as follows:

When submitting the "user login" request at the current end, the user name and password will be submitted to the login method corresponding to the back-end User Controller controller in the form of "submitting form". This method will first make the most basic parameter judgment and verification, and after the verification is passed, Shiro's built-in component SecurityUtils. getSubject Utils will be invoked.( ) The. login () method performs the login operation, in which the login operation will be performed mainly in the "doGetAuthenticationInfo method of the custom Realm".

(3) Next, we develop a custom Realm based on Shiro's Authentication Realm and implement the user login authentication method, doGetAuthentication Info () method. Its complete source code is as follows:

/**
 * User-defined realm-for shiro authentication and authorization
 * @Author:debug (SteadyJack)
 * @Date: 2019/7/2 17:55
 **/
public class CustomRealm extends AuthorizingRealm{
    private static final Logger log= LoggerFactory.getLogger(CustomRealm.class);

    private static final Long sessionKeyTimeOut=3600_000L;
    @Autowired
    private UserMapper userMapper;

    //To grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //Authentication-Logon
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
        String userName=token.getUsername();
        String password=String.valueOf(token.getPassword());
        log.info("User name currently logged in={} Password={} ",userName,password);

        User user=userMapper.selectByUserName(userName);
        if (user==null){
            throw new UnknownAccountException("user name does not exist!");
        }
        if (!Objects.equals(1,user.getIsActive().intValue())){
            throw new DisabledAccountException("The current user has been disabled!");
        }
        if (!user.getPassword().equals(password)){
            throw new IncorrectCredentialsException("User name password mismatch!");
        }

        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user.getUserName(),password,getName());
        setSession("uid",user.getId());
        return info;
    }

    /**
     * Put the key and the corresponding value into shiro's session - and eventually give it to HttpSession for management (if it's a distributed session configuration, redis for management)
     * @param key
     * @param value
     */
    private void setSession(String key,Object value){
        Session session=SecurityUtils.getSubject().getSession();
        if (session!=null){
            session.setAttribute(key,value);
            session.setTimeout(sessionKeyTimeOut);
        }
    }
}

Among them, userMapper.selectByUserName(userName) is mainly used to query user entity information according to userName, and its corresponding dynamic Sql is written as follows:

<!--Query by username-->
<select id="selectByUserName" resultType="com.debug.kill.model.entity.User">
  SELECT <include refid="Base_Column_List"/>
  FROM user
  WHERE user_name = #{userName}
</select>

It is worth mentioning that when the user logs in successfully (that is, the user name and password values match the user table of the database), we will use Shiro's Session session mechanism to store the current user's information into the server session and cache it for a certain time! (In this case, 3600s, or 1 hour)!

(4) Finally, we need to achieve "how can users automatically detect whether they are logged in when accessing the details of goods to be killed or when they rush to buy goods or any business requests that need to be intercepted?" If it has logged in, it directly enters the method logic corresponding to the business request. Otherwise, it needs to go to the user login page and ask the user to log in.

Based on this requirement, we need to use Shiro's component ShiroFilterFactoryBean to judge whether the user is logged in or not, and use FilterChain Definition Map to intercept some link URL s that need authorization. The complete source code is as follows:

/**
 * shiro Universal configuration
 * @Author:debug (SteadyJack)
 * @Date: 2019/7/2 17:54
 **/
@Configuration
public class ShiroConfig {

    @Bean
    public CustomRealm customRealm(){
        return new CustomRealm();
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        bean.setLoginUrl("/to/login");
        bean.setUnauthorizedUrl("/unauth");
       //Intercept some authorized link URL s
        Map<String, String> filterChainDefinitionMap=new HashMap<>();
        filterChainDefinitionMap.put("/to/login","anon");
        filterChainDefinitionMap.put("/**","anon");

        filterChainDefinitionMap.put("/kill/execute","authc");
        filterChainDefinitionMap.put("/item/detail/*","authc");

        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

}

As can be seen from the above source code, Shiro's ShiroFilterFactoryBean component will intercept links between URL=/kill/execute and URL=/item/detail/* that is, when users access these URLs, the system will require the current user to log in (provided that the user has not logged in yet! If you have logged in, skip it and go to the actual business module!

In addition, Shiro's ShiroFilterFactoryBean component also has links to "Go to the login page" and "Adjustment page without user authorization/login", respectively, / to/login and / unauth!

(5) So far, the front-end and back-end code of integrating Shiro framework to realize user's login authentication has been completed. Running the project/system in the external tomcat server, opening the browser, you can access the "list page of goods to be killed in seconds" and click "details", at this time, because the user has not logged in, it will jump. To the user login page, as shown in the following figure:

Enter the user name: debug, password: 123456, click the "Log in" button, you can log in successfully, and successfully enter the "Details Page", as shown in the following figure:

After successful login, go back to the previous list page, i.e. the list page of goods to be killed in seconds, click the "details" button again, and then go directly to the "details page of goods to be killed in seconds", instead of jumping to the "user login page", and the login status of users will last for an hour! This is achieved by Shiro's Session.

Supplement:

1. At present, the whole construction and code battle of this second kill system have been completed. The complete source code database address can be downloaded here: https://gitee.com/steadyjack/SpringBoot-SecondKill Remember Fork and Star!!!

2. Because the updates of the corresponding blogs may not be very fast, if you want to get a quick start and a real-world system, you can consider contacting Debug to get the complete video tutorial of the "Java Secondary Killing System" (the course is charged!). Of course, you can also click on the following link https://gitee.com/steadyjack/SpringBoot-SecondKill Contact Debug or join the corresponding technology exchange group for communication!

Keywords: Java Shiro Session Database Apache

Added by marinedalek on Sun, 21 Jul 2019 10:13:20 +0300