Springboot front and back end separation - Shiro+Md5 encryption and authentication login ----- learning record

Springboot front and back end separation - integration of Shiro-Md5 encryption and authentication login - Learning Records

	Reference here: https://blog.csdn.net/bbxylqf126com/article/details/110501155
				https://blog.csdn.net/weixin_42375707/article/details/111145907
				https://blog.csdn.net/qq_34845394/article/details/94858168

I am confused and smart - Zhang Tongxue.
Currently studying shiro, I have studied spring security before. It's easier to say the former than the latter on the Internet

rely on

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <!-- SpringBootText Annotation dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Junit rely on -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

        <!--Hot deployment-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--journal-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--        <dependency>-->
        <!--            <groupId>org.slf4j</groupId>-->
        <!--            <artifactId>slf4j-log4j12</artifactId>-->
        <!--            <version>1.7.21</version>-->
        <!--        </dependency>-->
        <!--        <dependency>-->
        <!--            <groupId>org.slf4j</groupId>-->
        <!--            <artifactId>jcl-over-slf4j</artifactId>-->
        <!--            <version>1.7.21</version>-->
        <!--        </dependency>-->
        <!--        <dependency>-->
        <!--            <groupId>commons-logging</groupId>-->
        <!--            <artifactId>commons-logging</artifactId>-->
        <!--            <version>1.1.3</version>-->
        <!--        </dependency>-->
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <!-- Swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>

Directly on the code (I think the key)

shiro core configuration

package com.ztxue.mybatis_plus.shiro;

import com.ztxue.mybatis_plus.utils.ShiroConstant;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro The core configuration class is used to integrate shiro framework
 */
@Configuration
public class ShiroConfiguration {
    //1. Import custom realm customerrealm ()
    @Bean
    public Realm getRealm() {
        ShiroRealm shiroRealm = new ShiroRealm();
        // Set password matcher
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        // Set encryption method
        credentialsMatcher.setHashAlgorithmName(ShiroConstant.HASH_ALGORITHM_NAME.MD5);
        // Sets the number of hashes
        credentialsMatcher.setHashIterations(ShiroConstant.HASH_ITERATORS);
        shiroRealm.setCredentialsMatcher(credentialsMatcher);
        return shiroRealm;
    }
    //2. Create security manager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm)  {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //Associated realm
        defaultWebSecurityManager.setRealm(realm);
        return defaultWebSecurityManager;
    }
    //3. Create a filter factory - responsible for intercepting all requests
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //Set up security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        //Filter chain mapping
        Map<String, String> filter = new LinkedHashMap<>();
        /*
         * Common filters are as follows
         * anon: Access without authentication
         * authc: Must be authenticated to access
         * user: Remember, I can only access it when I open it
         * perms: You must have permission on a resource to access it
         * */
        filter.put("/user/login","anon");
        filter.put("/user/regis","anon");
        // Configure the link order judgment that will not be intercepted. It must be configured to each static directory
        filter.put("/swagger-ui.html/**", "anon");
        filter.put("/webjars/**","anon");
        // All url blocking
//        filter.put("/**", "authc");
        // Configure the exit filter. Shiro has implemented the specific exit code for us. The location is under anon and authc
        filter.put("/user/logout", "logout");
        // Modify shiro's default login address. After successful login, return the user's basic information and token to the front end
        bean.setLoginUrl("/user/login");
        // Set the link to jump after success
        bean.setSuccessUrl("/user/regis");
        // Block unauthorized paths
        bean.setUnauthorizedUrl("/user/unauthorized");
        //Filter chain value
        bean.setFilterChainDefinitionMap(filter);
        return bean;
    }
}

Custom realm

package com.ztxue.mybatis_plus.shiro;

import com.ztxue.mybatis_plus.fv_sys.service.user.FvUserService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 * Custom realm -- discard ini file, using database query
 */
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    FvUserService userService;
    @Autowired
    FvRoleService roleService;
    @Autowired
    FvPermissionService permissionService;

    // to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    }
    // authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //Get the user's entered account
        String userName = (String) token.getPrincipal();
        System.out.println("userName----------------------------->>>" + userName);
        //Find the User object from the database by username
        //In the actual project, caching can be done according to the actual situation. If not, Shiro also has a time interval mechanism, and the method will not be repeated within 2 minutes
        FvUser user = userService.findByName(userName);
        System.out.println("user.getUPassword()----------------------------->>>" + user.getUPassword());
        System.out.println("userName----------------------------->>>" + userName);

        if (!ObjectUtils.isEmpty(user)) {
            return new SimpleAuthenticationInfo(
                    // You can also write the user name
                    user,
                    // The password passed in is the password obtained from the database, and then it is compared and matched with the password in the token
                    user.getUPassword(),
                    // salt – used for encryption password comparison. If not required, it can be set to empty ""
                    ByteSource.Util.bytes(user.getUSalt()),
                    // The name of the current realm
                    getName()
            );
        }
        return null;
    }
}

Pay attention to this place

new SimpleAuthenticationInfo(
                    // You can also write the user name
                    user,
                    // The password passed in is the password obtained from the database, and then it is compared and matched with the password in the token
                    user.getUPassword(),
                    // salt – used for encryption password comparison. If not required, it can be set to empty ""
                    ByteSource.Util.bytes(user.getUSalt()),
                    // The name of the current realm
                    getName()
            )

There are three or four parameters in SimpleAuthenticationInfo, and the third – bytesource Util. Bytes (user. Getusalt()), get the salt value

Random salt generation tool class (collectable learning)

package com.ztxue.mybatis_plus.utils;

import java.util.Random;

/**
 * User random salt generation tool class
 */
public class SaltUtil {
    /**
     * Static method for generating salt
     * @param n
     * @return
     */
    public static String getSalt(int n){
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890!@#$%^&*()".toCharArray();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            char aChar = chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }
        return sb.toString();
    }
}

MD5 encryption description class (defined as a constant. I don't think duck is necessary. Maybe my level is not enough)

package com.ztxue.mybatis_plus.utils;

public class ShiroConstant {
    /** Number of random salt**/
    public static final int SALT_LENGTH = 8;
    /** hash Number of hashes**/
    public static final int HASH_ITERATORS = 1024;
    /** Encryption mode**/
    public interface HASH_ALGORITHM_NAME {
        String MD5 = "MD5";
    }
}

Next is the "four brothers"

controller key code

package com.ztxue.mybatis_plus.fv_sys.controller.user;
import com.ztxue.mybatis_plus.config.exception.LoginException;
import com.ztxue.mybatis_plus.fv_sys.entity.user.FvUser;
import com.ztxue.mybatis_plus.fv_sys.mapper.user.UserMapper;
import com.ztxue.mybatis_plus.fv_sys.service.user.FvUserService;
import com.ztxue.mybatis_plus.result.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * <p>
 * Front end controller
 * </p>
 *
 * @author Zhang Tongxue
 * @since 2021-07-17
 */
@RestController
@RequestMapping("/user")
@Api(description = "User page")
public class FvUserController {

    @Autowired
    FvUserService fvUserService;
    @Autowired
    UserMapper userMapper;


    @ApiOperation("register")
    @PostMapping("/regis")
    public AjaxResult register(FvUser user) {
        try {
            fvUserService.register(user);
            return AjaxResult.success("Registration succeeded!",user);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("Registration failed! Great Xia, please start all over again!");
        }
    }

    @ApiOperation("Sign in")
    @RequestMapping("/login")
    public AjaxResult login(String userName, String password) {

        // Get Subject instance object, user instance
        Subject currentUser = SecurityUtils.getSubject();
        // Encapsulate user name and password into UsernamePasswordToken
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);

        System.out.println("token============>" + token);
        try {
            // The method passed to the MyShiroRealm class for authentication
            currentUser.login(token);
            return AjaxResult.success(token);
        } catch (UnknownAccountException e) {
            throw new LoginException("Account does not exist!", e);
        } catch (IncorrectCredentialsException e) {
            throw new LoginException("Incorrect password!", e);
        } catch (AuthenticationException e) {
            throw new LoginException("User authentication failed!", e);
        }
        // User information returned after successful login
    }
}

The encapsulated result set is used here

package com.ztxue.mybatis_plus.result;


import com.ztxue.mybatis_plus.utils.HttpStatus;
import com.ztxue.mybatis_plus.utils.StringUtils;

import java.util.HashMap;

/**
 * Operation message reminder
 * */
public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    /**
     * Status code
     */
    public static final String CODE_TAG = "code";

    /**
     * Return content
     */
    public static final String MSG_TAG = "msg";

    /**
     * data object
     */
    public static final String DATA_TAG = "data";

    /**
     * Initialize a newly created Ajax result object to represent an empty message.
     */
    public AjaxResult() {
    }

    /**
     * Initialize a newly created Ajax result object
     *
     * @param code Status code
     * @param msg  Return content
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * Initialize a newly created Ajax result object
     *
     * @param code Status code
     * @param msg  Return content
     * @param data data object
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * Return success message
     *
     * @return Success message
     */
    public static AjaxResult success() {
        return AjaxResult.success("Operation succeeded");
    }

    /**
     * Return success data
     *
     * @return Success message
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("Operation succeeded", data);
    }

    /**
     * Return success message
     *
     * @param msg Return content
     * @return Success message
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * Return success message
     *
     * @param msg  Return content
     * @param data data object
     * @return Success message
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * Return error message
     *
     * @return
     */
    public static AjaxResult error() {
        return AjaxResult.error("operation failed");
    }

    /**
     * Return error message
     *
     * @param msg Return content
     * @return Warning message
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * Return error message
     *
     * @param msg  Return content
     * @param data data object
     * @return Warning message
     */
    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * Return error message
     *
     * @param code Status code
     * @param msg  Return content
     * @return Warning message
     */
    public static AjaxResult error(int code, String msg) {
        return new AjaxResult(code, msg, null);
    }
}

serviceImpl part

@Autowired
    UserMapper userMapper;


    @Override
    public FvUser findByName(String name) {
        return userMapper.findByName(name);
    }
    // register
    @Override
    public void register(FvUser user) {
        // Generate random salt
        String salt = SaltUtil.getSalt(ShiroConstant.SALT_LENGTH);
        // Save random salt
        user.setUSalt(salt);
        // Generate password
        Md5Hash password = new Md5Hash(user.getUPassword(), salt, ShiroConstant.HASH_ITERATORS);
        // Save password
        user.setUPassword(password.toHex());
        userMapper.insert(user);
        System.out.println("Generated salt------------------>"+salt);
        System.out.println("MD5 Encrypted password------------------>"+password);
    }

serivce section

	FvUser findByName(String name);
    // register
    void register(FvUser user);

mapper

package com.ztxue.mybatis_plus.fv_sys.mapper.user;

import com.ztxue.mybatis_plus.fv_sys.entity.user.FvUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

/**
 * <p>
 *  Mapper Interface
 * </p>
 *
 * @author Zhang Tongxue
 * @since 2021-07-17
 */
@Mapper
public interface UserMapper extends BaseMapper<FvUser> {

    @Select("select * from fv_user where u_name = #{uName}")
    FvUser findByName(String name);
    @Insert("INSERT INTO fv_user ( u_name, u_password, is_deleted, gmt_create, gmt_modified, u_salt ) VALUES (#{uName},#{uPassword},#{isDeleted},#{gmtCreate},#{gmtModified},#{uSalt})")
    int add(FvUser user);
}

Actually, I used mybater_ Plus, which inherits its basic mapper. Many methods don't need to be written, but I'm not very familiar with them.
Then this is my entity class and database

package com.ztxue.mybatis_plus.fv_sys.entity.user;

import com.baomidou.mybatisplus.annotation.*;

import java.util.Date;

import java.io.Serializable;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 *
 * </p>
 *
 * @author Zhang Tongxue
 * @since 2021-07-17
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class FvUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * User id
     */
    @TableId(value = "u_id", type = IdType.AUTO)
    private Integer uId;
    /**
     * user name
     */
    private String uName;
    /**
     * User mobile phone
     */
    private String uPhone;
    /**
     * mailbox
     */
    public String uEmail;
    /**
     * password
     */
    public String uPassword;
    /**
     * Logical deletion
     */
    @TableLogic
    @TableField(fill = FieldFill.INSERT)
    private Integer isDeleted;
    /**
     * Creation time
     */
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;
    /**
     * Modification time
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;
    /**
     * Password salt Redefine the salt, user name + salt, so it is not easy to crack. You can define salt in a variety of ways
     */
    @TableField(value = "u_salt")
    private String uSalt;
}

CREATE TABLE `fv_user` (
  `u_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'user id',
  `u_name` varchar(255) DEFAULT NULL COMMENT 'user name',
  `u_phone` varchar(255) DEFAULT NULL COMMENT 'User mobile phone',
  `u_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'mailbox',
  `u_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'password',
  `u_salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'salt',
  `u_state` int NOT NULL DEFAULT '1' COMMENT 'state',
  `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT 'Logical deletion',
  `gmt_create` datetime DEFAULT NULL COMMENT 'Creation time',
  `gmt_modified` datetime DEFAULT NULL COMMENT 'Modification time',
  PRIMARY KEY (`u_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Ignore some prefixes and some useless fields

Write it at the back

Bloggers are learning shiro at present, because they practice in the summer vacation, and then the internship enterprise uses shiro, not spring security, and the front and rear ends of the enterprise are separated. I happen to be unfamiliar with the separation of the front and rear ends, so I learned it together, but I encountered many difficulties.

To separate the front and back ends, I need to write an API. To test the API, I use the swagger (it's nice to learn and use now) framework and the software APIPost (I'm really not used to Postman).
shiro is currently learning basic authentication + MD5 encryption, which makes me dizzy, because the blogger himself is "smart" and always aims high.
The MD5 encryption logic is not complex. It is encrypted and stored in the database during "user registration" and the encrypted password taken from the database during "user authentication". shiro can analyze it. Isn't it?

There will also be shiro's learning records later, which is vx: handsomeztx. I hope to have small partners to learn and exchange, and big guys to criticize and teach.

Keywords: Java Spring Boot intellij-idea

Added by Dang on Sat, 15 Jan 2022 06:23:35 +0200