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.