Implementation of springboot integrating shiro multi authentication login function (account password login and mobile phone verification code login)

  • 2021-10-25 06:49:52
  • OfStack

1. First, create a new shiroConfig shiro configuration class with the following code:


@Configuration
public class SpringShiroConfig {


    /**
     * @param realms  The interface collection is used here to implement the multi-authentication login 
     * @return
     */
    @Bean
    public SecurityManager securityManager(Collection<Realm> realms) {
        DefaultWebSecurityManager sManager = new DefaultWebSecurityManager();
        sManager.setRealms(realms);
        return sManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager) {
        ShiroFilterFactoryBean sfBean = new ShiroFilterFactoryBean();
        sfBean.setSecurityManager(securityManager);
        // If it is anonymous access, the location of the resource jump that cannot be accessed is accessed 
        sfBean.setLoginUrl("/index");
        // Definition map Specify request filtering rules ( Which resources allow anonymous access , Which must authenticate access )
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        // Static resources allow anonymous access :"anon"  Static resource authorization cannot be written static All the following openings should be static All folders below 1 A 1 An open, templates Likewise 
        //map Adj. key It can be the location of the file or the path of the request 
        map.put("/bower_components/**", "anon");
        map.put("/json/**", "anon");
        map.put("/pages", "anon");
        map.put("/user/userPasswordLogin", "anon");
        map.put("/user/login", "anon");
        map.put("/user/reg", "anon");
        // When accessing this path, you will not enter controller Will intercept and exit directly here, ask why, and want to request the process to go 
        map.put("/user/userLogout", "logout");
        // Intercept all request paths except the above 
        map.put("/**", "user");
        sfBean.setFilterChainDefinitionMap(map);
        return sfBean;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

2. Write the implementation class of Realms, which inherits from AuthorizingRealm (this is to realize user name and password login). The code is as follows:


@Service
public class ShioUserRealm extends AuthorizingRealm {

    // Injection userdao
    @Autowired
    private UserDao userDao;
    /**
     *  Setting up the credential matcher 
     *
     * @param credentialsMatcher
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        /* Set up here MD5 Salt value encryption, you must use it here HashedCredentialsMatcher To have the following two methods */
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // Here is how to set the encryption 
        matcher.setHashAlgorithmName("MD5");
        // Here is the number of times to set encryption 
        matcher.setHashIterations(2);
        super.setCredentialsMatcher(matcher);
    }

    /**
     *  Here is the authorization set 
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        return null;
    }

    /**
     *  Acquisition and encapsulation of authentication data are accomplished by this method. The bottom layer of the system transmits the authentication data to the authentication manager, and the authentication manager completes the authentication operation 
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // First, determine whether this is the data from this token: we have divided it into UsernamePasswordToken ( shiro Provided to us.), UserPhoneToken
        if (!(authenticationToken instanceof UsernamePasswordToken)) {
            return null;
        }
        // Get controller Incoming data 
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        //upToken.setRememberMe(true);shiro Default to false, Is whether to remember my function 
        // Submitted here for the user username
        String username = upToken.getUsername();
        // De-data is more name Get the user's information 
        User user = userDao.findUserByUserName(username);
        // Determine whether the database has this user 
        if (user == null) {
            throw new UnknownAccountException();
        }
        // Determining whether the user's status is disabled ( Fields of the database )
        if (user.getState() == 0) {
            throw new LockedAccountException();
        }
        // Here is the salt value in the user information, and the salt value should be converted to ByteSource This type can only be used 
        ByteSource credentialsSalt = ByteSource.Util.bytes(user.getSalt());
        // Here is to give this user's information to shiro ( user For the user object, user.getPassword() Is the object to encrypt, credentialsSalt Is the salt value, getName() Current object) 
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), credentialsSalt, getName());
        return info;
    }
}

3. At this time, the user's account password login can already be used. The controller code is as follows:


@RequestMapping("userPasswordLogin")
    @ResponseBody
    public JsonResult userPasswordLogin(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        subject.login(token);
        return new JsonResult("login Ok");
    }

4. Let's realize SMS verification code login now:

4.1 Write UserPhoneToken first, and I put it in the same directory as l and springShiroConfig:


@Component
public class UserPhoneToken extends UsernamePasswordToken implements Serializable {

    private static final long serialVersionUID = 6293390033867929958L;
    //  Mobile phone number 
    private String phoneNum;
    // Parametric structure 
    public UserPhoneToken(){}
    
    // Gets the value saved 
    @Override
    public Object getPrincipal() {
        if (phoneNum == null) {
            return getUsername();
        } else {
            return getPhoneNum();
        }
    }

    @Override
    public Object getCredentials() {
        if (phoneNum == null) {
            return getPassword();
        }else {
            return "ok";
        }

    }

    public UserPhoneToken(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    public UserPhoneToken(final String userName, final String password) {
        super(userName, password);
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }
    @Override
    public String toString() {
        return "PhoneToken [PhoneNum=" + phoneNum + "]";
    }

}

4.2 In writing shiroUserPhoneRealm, the code is as follows:


@Service
public class ShioUserPhoneRealm extends AuthorizingRealm {

    @Autowired
    private UserDao userDao;

    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        // Here's CredentialsMatcher Adj. new The object of must be AllowAllCredentialsMatcher
        CredentialsMatcher matcher = new AllowAllCredentialsMatcher();
        super.setCredentialsMatcher(matcher);
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     *  Acquisition and encapsulation of authentication data are accomplished by this method. The bottom layer of the system transmits the authentication data to the authentication manager, and the authentication manager completes the authentication operation 
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UserPhoneToken token = null;
        if (authenticationToken instanceof UserPhoneToken) {
            token = (UserPhoneToken) authenticationToken;
        }else {
            return null;
        }
        // Get the verification code I sent is stored in session Verification code and mobile phone number in 
        String verificationCode = (String) SecurityUtils.getSubject().getSession().getAttribute("verificationCode");
        String phone = (String) SecurityUtils.getSubject().getSession().getAttribute("phone");
        // Get controller Incoming data 
        String verificationCode1 = (String) token.getPrincipal();
        // Go to the database to query the user information according to the mobile phone number 
        User user = userDao.findUserByUserPhone(phone);
        if (StringUtils.isEmpty(verificationCode)) {
            throw new ServiceException(" Network error ");
        }
        // Compare mobile phone numbers 
        if (!verificationCode.equals(verificationCode1)) {
            throw new ServiceException(" Incorrect verification code ");
        }
        if (user == null) {
            throw new UnknownAccountException();
        }
        if (user.getState() == 0) {
            throw new LockedAccountException();
        }
        return new SimpleAuthenticationInfo(user,phone,getName());
    }
}

4.3 Mobile phone number login verification has been basically completed: controller code is as follows:


@PostMapping("verificationCodeLogin")
    @ResponseBody
    public JsonResult verificationCodeLogin(String password) {
        Subject subject = SecurityUtils.getSubject();
        UserPhoneToken token = new UserPhoneToken(password);
        subject.login(token);
        return new JsonResult("login OK");
    }

bug encountered during use

1.

org. apache. shiro. authc. UnknownAccountException: Realm [cn. tedu. wxacs. service. impl @ 768d8431] was unable to account the the submitted AuthenticationToken [org. apache. shiro. authc-Zhang 3, rememberMe=false].

This problem is mine because one of the implementation classes in Realm is not annotated, which I demonstrated here is @ Service annotated instead of ShiroUserRealm

2.

org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens.

The problem here is that it should be User user = userDao. findUserByUserName (username) for my ShioUserRealm's AuthenticationInfo method; The problem occurred in this line of code, when debug, it was found that this sentence was error-guaranteed after execution

Reason: Because my application. yml file does not have the path to write the mapper file corresponding to dao

3. After the position of new SimpleAuthenticationInfo (user, phone, getName ()) in doGetAuthenticationInfo method of ShioUserPhoneRealm, you will report that the error should be ShioUserPhoneRealm. In this method, you did not set the object of new to AllowAllCredentialsMatcher ();


@Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        // Here's CredentialsMatcher Adj. new The object of must be AllowAllCredentialsMatcher
        CredentialsMatcher matcher = new AllowAllCredentialsMatcher();
        super.setCredentialsMatcher(matcher);
    }

There are 1 points that need attention in the annotation. It is suggested to see. If the annotation is wrong, I hope to point out or contact me in the decentralized comments

Should be my limited knowledge, this method to achieve my current no problem, which is what is wrong with the place also hope that you point out, thank you!

jdk 8, spring version 2.2. 1 of boot, shiro version 1,. 4.1


Related articles: